Home | History | Annotate | Download | only in android
      1 package com.jme3.input.android;
      2 
      3 import android.content.Context;
      4 import android.opengl.GLSurfaceView;
      5 import android.util.AttributeSet;
      6 import android.view.GestureDetector;
      7 import android.view.KeyEvent;
      8 import android.view.MotionEvent;
      9 import android.view.ScaleGestureDetector;
     10 import com.jme3.input.KeyInput;
     11 import com.jme3.input.RawInputListener;
     12 import com.jme3.input.TouchInput;
     13 import com.jme3.input.event.MouseButtonEvent;
     14 import com.jme3.input.event.MouseMotionEvent;
     15 import com.jme3.input.event.TouchEvent;
     16 import com.jme3.input.event.TouchEvent.Type;
     17 import com.jme3.math.Vector2f;
     18 import com.jme3.util.RingBuffer;
     19 import java.util.HashMap;
     20 import java.util.logging.Logger;
     21 
     22 /**
     23  * <code>AndroidInput</code> is one of the main components that connect jme with android. Is derived from GLSurfaceView and handles all Inputs
     24  * @author larynx
     25  *
     26  */
     27 public class AndroidInput extends GLSurfaceView implements
     28         TouchInput,
     29         GestureDetector.OnGestureListener,
     30         GestureDetector.OnDoubleTapListener,
     31         ScaleGestureDetector.OnScaleGestureListener {
     32 
     33     final private static int MAX_EVENTS = 1024;
     34     // Custom settings
     35     public boolean mouseEventsEnabled = true;
     36     public boolean mouseEventsInvertX = false;
     37     public boolean mouseEventsInvertY = false;
     38     public boolean keyboardEventsEnabled = false;
     39     public boolean dontSendHistory = false;
     40     // Used to transfer events from android thread to GLThread
     41     final private RingBuffer<TouchEvent> eventQueue = new RingBuffer<TouchEvent>(MAX_EVENTS);
     42     final private RingBuffer<TouchEvent> eventPoolUnConsumed = new RingBuffer<TouchEvent>(MAX_EVENTS);
     43     final private RingBuffer<TouchEvent> eventPool = new RingBuffer<TouchEvent>(MAX_EVENTS);
     44     final private HashMap<Integer, Vector2f> lastPositions = new HashMap<Integer, Vector2f>();
     45     // Internal
     46     private ScaleGestureDetector scaledetector;
     47     private GestureDetector detector;
     48     private int lastX;
     49     private int lastY;
     50     private final static Logger logger = Logger.getLogger(AndroidInput.class.getName());
     51     private boolean isInitialized = false;
     52     private RawInputListener listener = null;
     53     private static final int[] ANDROID_TO_JME = {
     54         0x0, // unknown
     55         0x0, // key code soft left
     56         0x0, // key code soft right
     57         KeyInput.KEY_HOME,
     58         KeyInput.KEY_ESCAPE, // key back
     59         0x0, // key call
     60         0x0, // key endcall
     61         KeyInput.KEY_0,
     62         KeyInput.KEY_1,
     63         KeyInput.KEY_2,
     64         KeyInput.KEY_3,
     65         KeyInput.KEY_4,
     66         KeyInput.KEY_5,
     67         KeyInput.KEY_6,
     68         KeyInput.KEY_7,
     69         KeyInput.KEY_8,
     70         KeyInput.KEY_9,
     71         KeyInput.KEY_MULTIPLY,
     72         0x0, // key pound
     73         KeyInput.KEY_UP,
     74         KeyInput.KEY_DOWN,
     75         KeyInput.KEY_LEFT,
     76         KeyInput.KEY_RIGHT,
     77         KeyInput.KEY_RETURN, // dpad center
     78         0x0, // volume up
     79         0x0, // volume down
     80         KeyInput.KEY_POWER, // power (?)
     81         0x0, // camera
     82         0x0, // clear
     83         KeyInput.KEY_A,
     84         KeyInput.KEY_B,
     85         KeyInput.KEY_C,
     86         KeyInput.KEY_D,
     87         KeyInput.KEY_E,
     88         KeyInput.KEY_F,
     89         KeyInput.KEY_G,
     90         KeyInput.KEY_H,
     91         KeyInput.KEY_I,
     92         KeyInput.KEY_J,
     93         KeyInput.KEY_K,
     94         KeyInput.KEY_L,
     95         KeyInput.KEY_M,
     96         KeyInput.KEY_N,
     97         KeyInput.KEY_O,
     98         KeyInput.KEY_P,
     99         KeyInput.KEY_Q,
    100         KeyInput.KEY_R,
    101         KeyInput.KEY_S,
    102         KeyInput.KEY_T,
    103         KeyInput.KEY_U,
    104         KeyInput.KEY_V,
    105         KeyInput.KEY_W,
    106         KeyInput.KEY_X,
    107         KeyInput.KEY_Y,
    108         KeyInput.KEY_Z,
    109         KeyInput.KEY_COMMA,
    110         KeyInput.KEY_PERIOD,
    111         KeyInput.KEY_LMENU,
    112         KeyInput.KEY_RMENU,
    113         KeyInput.KEY_LSHIFT,
    114         KeyInput.KEY_RSHIFT,
    115         //        0x0, // fn
    116         //        0x0, // cap (?)
    117 
    118         KeyInput.KEY_TAB,
    119         KeyInput.KEY_SPACE,
    120         0x0, // sym (?) symbol
    121         0x0, // explorer
    122         0x0, // envelope
    123         KeyInput.KEY_RETURN, // newline/enter
    124         KeyInput.KEY_DELETE,
    125         KeyInput.KEY_GRAVE,
    126         KeyInput.KEY_MINUS,
    127         KeyInput.KEY_EQUALS,
    128         KeyInput.KEY_LBRACKET,
    129         KeyInput.KEY_RBRACKET,
    130         KeyInput.KEY_BACKSLASH,
    131         KeyInput.KEY_SEMICOLON,
    132         KeyInput.KEY_APOSTROPHE,
    133         KeyInput.KEY_SLASH,
    134         KeyInput.KEY_AT, // at (@)
    135         KeyInput.KEY_NUMLOCK, //0x0, // num
    136         0x0, //headset hook
    137         0x0, //focus
    138         KeyInput.KEY_ADD,
    139         KeyInput.KEY_LMETA, //menu
    140         0x0,//notification
    141         0x0,//search
    142         0x0,//media play/pause
    143         0x0,//media stop
    144         0x0,//media next
    145         0x0,//media previous
    146         0x0,//media rewind
    147         0x0,//media fastforward
    148         0x0,//mute
    149     };
    150 
    151     public AndroidInput(Context ctx, AttributeSet attribs) {
    152         super(ctx, attribs);
    153         detector = new GestureDetector(null, this, null, false);
    154         scaledetector = new ScaleGestureDetector(ctx, this);
    155 
    156     }
    157 
    158     public AndroidInput(Context ctx) {
    159         super(ctx);
    160         detector = new GestureDetector(null, this, null, false);
    161         scaledetector = new ScaleGestureDetector(ctx, this);
    162     }
    163 
    164     private TouchEvent getNextFreeTouchEvent() {
    165         return getNextFreeTouchEvent(false);
    166     }
    167 
    168     /**
    169      * Fetches a touch event from the reuse pool
    170      * @param wait if true waits for a reusable event to get available/released
    171      * by an other thread, if false returns a new one if needed.
    172      *
    173      * @return a usable TouchEvent
    174      */
    175     private TouchEvent getNextFreeTouchEvent(boolean wait) {
    176         TouchEvent evt = null;
    177         synchronized (eventPoolUnConsumed) {
    178             int size = eventPoolUnConsumed.size();
    179             while (size > 0) {
    180                 evt = eventPoolUnConsumed.pop();
    181                 if (!evt.isConsumed()) {
    182                     eventPoolUnConsumed.push(evt);
    183                     evt = null;
    184                 } else {
    185                     break;
    186                 }
    187                 size--;
    188             }
    189         }
    190 
    191         if (evt == null) {
    192             if (eventPool.isEmpty() && wait) {
    193                 logger.warning("eventPool buffer underrun");
    194                 boolean isEmpty;
    195                 do {
    196                     synchronized (eventPool) {
    197                         isEmpty = eventPool.isEmpty();
    198                     }
    199                     try {
    200                         Thread.sleep(50);
    201                     } catch (InterruptedException e) {
    202                     }
    203                 } while (isEmpty);
    204                 synchronized (eventPool) {
    205                     evt = eventPool.pop();
    206                 }
    207             } else if (eventPool.isEmpty()) {
    208                 evt = new TouchEvent();
    209                 logger.warning("eventPool buffer underrun");
    210             } else {
    211                 synchronized (eventPool) {
    212                     evt = eventPool.pop();
    213                 }
    214             }
    215         }
    216         return evt;
    217     }
    218 
    219     /**
    220      * onTouchEvent gets called from android thread on touchpad events
    221      */
    222     @Override
    223     public boolean onTouchEvent(MotionEvent event) {
    224         boolean bWasHandled = false;
    225         TouchEvent touch;
    226         //    System.out.println("native : " + event.getAction());
    227         int action = event.getAction() & MotionEvent.ACTION_MASK;
    228         int pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK)
    229                 >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
    230         int pointerId = event.getPointerId(pointerIndex);
    231 
    232         // final int historySize = event.getHistorySize();
    233         //final int pointerCount = event.getPointerCount();
    234 
    235         switch (action) {
    236             case MotionEvent.ACTION_POINTER_DOWN:
    237             case MotionEvent.ACTION_DOWN:
    238                 touch = getNextFreeTouchEvent();
    239                 touch.set(Type.DOWN, event.getX(pointerIndex), this.getHeight() - event.getY(pointerIndex), 0, 0);
    240                 touch.setPointerId(pointerId);
    241                 touch.setTime(event.getEventTime());
    242                 touch.setPressure(event.getPressure(pointerIndex));
    243                 processEvent(touch);
    244 
    245                 bWasHandled = true;
    246                 break;
    247 
    248             case MotionEvent.ACTION_POINTER_UP:
    249             case MotionEvent.ACTION_CANCEL:
    250             case MotionEvent.ACTION_UP:
    251 
    252                 touch = getNextFreeTouchEvent();
    253                 touch.set(Type.UP, event.getX(pointerIndex), this.getHeight() - event.getY(pointerIndex), 0, 0);
    254                 touch.setPointerId(pointerId);
    255                 touch.setTime(event.getEventTime());
    256                 touch.setPressure(event.getPressure(pointerIndex));
    257                 processEvent(touch);
    258 
    259 
    260                 bWasHandled = true;
    261                 break;
    262             case MotionEvent.ACTION_MOVE:
    263 
    264 
    265                 // Convert all pointers into events
    266                 for (int p = 0; p < event.getPointerCount(); p++) {
    267                     Vector2f lastPos = lastPositions.get(pointerIndex);
    268                     if (lastPos == null) {
    269                         lastPos = new Vector2f(event.getX(pointerIndex), this.getHeight() - event.getY(pointerIndex));
    270                         lastPositions.put(pointerId, lastPos);
    271                     }
    272                     touch = getNextFreeTouchEvent();
    273                     touch.set(Type.MOVE, event.getX(pointerIndex), this.getHeight() - event.getY(pointerIndex), event.getX(pointerIndex) - lastPos.x, this.getHeight() - event.getY(pointerIndex) - lastPos.y);
    274                     touch.setPointerId(pointerId);
    275                     touch.setTime(event.getEventTime());
    276                     touch.setPressure(event.getPressure(pointerIndex));
    277                     processEvent(touch);
    278                     lastPos.set(event.getX(pointerIndex), this.getHeight() - event.getY(pointerIndex));
    279                 }
    280                 bWasHandled = true;
    281                 break;
    282             case MotionEvent.ACTION_OUTSIDE:
    283                 break;
    284 
    285         }
    286 
    287         // Try to detect gestures
    288         this.detector.onTouchEvent(event);
    289         this.scaledetector.onTouchEvent(event);
    290 
    291         return bWasHandled;
    292     }
    293 
    294     @Override
    295     public boolean onKeyDown(int keyCode, KeyEvent event) {
    296         TouchEvent evt;
    297         evt = getNextFreeTouchEvent();
    298         evt.set(TouchEvent.Type.KEY_DOWN);
    299         evt.setKeyCode(keyCode);
    300         evt.setCharacters(event.getCharacters());
    301         evt.setTime(event.getEventTime());
    302 
    303         // Send the event
    304         processEvent(evt);
    305 
    306         // Handle all keys ourself except Volume Up/Down
    307         if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP) || (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)) {
    308             return false;
    309         } else {
    310             return true;
    311         }
    312     }
    313 
    314     @Override
    315     public boolean onKeyUp(int keyCode, KeyEvent event) {
    316         TouchEvent evt;
    317         evt = getNextFreeTouchEvent();
    318         evt.set(TouchEvent.Type.KEY_UP);
    319         evt.setKeyCode(keyCode);
    320         evt.setCharacters(event.getCharacters());
    321         evt.setTime(event.getEventTime());
    322 
    323         // Send the event
    324         processEvent(evt);
    325 
    326         // Handle all keys ourself except Volume Up/Down
    327         if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP) || (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)) {
    328             return false;
    329         } else {
    330             return true;
    331         }
    332     }
    333 
    334     // -----------------------------------------
    335     // JME3 Input interface
    336     @Override
    337     public void initialize() {
    338         TouchEvent item;
    339         for (int i = 0; i < MAX_EVENTS; i++) {
    340             item = new TouchEvent();
    341             eventPool.push(item);
    342         }
    343         isInitialized = true;
    344     }
    345 
    346     @Override
    347     public void destroy() {
    348         isInitialized = false;
    349 
    350         // Clean up queues
    351         while (!eventPool.isEmpty()) {
    352             eventPool.pop();
    353         }
    354         while (!eventQueue.isEmpty()) {
    355             eventQueue.pop();
    356         }
    357     }
    358 
    359     @Override
    360     public boolean isInitialized() {
    361         return isInitialized;
    362     }
    363 
    364     @Override
    365     public void setInputListener(RawInputListener listener) {
    366         this.listener = listener;
    367     }
    368 
    369     @Override
    370     public long getInputTimeNanos() {
    371         return System.nanoTime();
    372     }
    373     // -----------------------------------------
    374 
    375     private void processEvent(TouchEvent event) {
    376         synchronized (eventQueue) {
    377             eventQueue.push(event);
    378         }
    379     }
    380 
    381     //  ---------------  INSIDE GLThread  ---------------
    382     @Override
    383     public void update() {
    384         generateEvents();
    385     }
    386 
    387     private void generateEvents() {
    388         if (listener != null) {
    389             TouchEvent event;
    390             MouseButtonEvent btn;
    391             int newX;
    392             int newY;
    393 
    394             while (!eventQueue.isEmpty()) {
    395                 synchronized (eventQueue) {
    396                     event = eventQueue.pop();
    397                 }
    398                 if (event != null) {
    399                     listener.onTouchEvent(event);
    400 
    401                     if (mouseEventsEnabled) {
    402                         if (mouseEventsInvertX) {
    403                             newX = this.getWidth() - (int) event.getX();
    404                         } else {
    405                             newX = (int) event.getX();
    406                         }
    407 
    408                         if (mouseEventsInvertY) {
    409                             newY = this.getHeight() - (int) event.getY();
    410                         } else {
    411                             newY = (int) event.getY();
    412                         }
    413 
    414                         switch (event.getType()) {
    415                             case DOWN:
    416                                 // Handle mouse down event
    417                                 btn = new MouseButtonEvent(0, true, newX, newY);
    418                                 btn.setTime(event.getTime());
    419                                 listener.onMouseButtonEvent(btn);
    420                                 // Store current pos
    421                                 lastX = -1;
    422                                 lastY = -1;
    423                                 break;
    424 
    425                             case UP:
    426                                 // Handle mouse up event
    427                                 btn = new MouseButtonEvent(0, false, newX, newY);
    428                                 btn.setTime(event.getTime());
    429                                 listener.onMouseButtonEvent(btn);
    430                                 // Store current pos
    431                                 lastX = -1;
    432                                 lastY = -1;
    433                                 break;
    434 
    435                             case MOVE:
    436                                 int dx;
    437                                 int dy;
    438                                 if (lastX != -1) {
    439                                     dx = newX - lastX;
    440                                     dy = newY - lastY;
    441                                 } else {
    442                                     dx = 0;
    443                                     dy = 0;
    444                                 }
    445                                 MouseMotionEvent mot = new MouseMotionEvent(newX, newY, dx, dy, 0, 0);
    446                                 mot.setTime(event.getTime());
    447                                 listener.onMouseMotionEvent(mot);
    448                                 lastX = newX;
    449                                 lastY = newY;
    450                                 break;
    451                         }
    452 
    453 
    454                     }
    455                 }
    456 
    457                 if (event.isConsumed() == false) {
    458                     synchronized (eventPoolUnConsumed) {
    459                         eventPoolUnConsumed.push(event);
    460                     }
    461 
    462                 } else {
    463                     synchronized (eventPool) {
    464                         eventPool.push(event);
    465                     }
    466                 }
    467             }
    468 
    469         }
    470     }
    471     //  --------------- ENDOF INSIDE GLThread  ---------------
    472 
    473     // --------------- Gesture detected callback events  ---------------
    474     public boolean onDown(MotionEvent event) {
    475         return false;
    476     }
    477 
    478     public void onLongPress(MotionEvent event) {
    479         TouchEvent touch = getNextFreeTouchEvent();
    480         touch.set(Type.LONGPRESSED, event.getX(), this.getHeight() - event.getY(), 0f, 0f);
    481         touch.setPointerId(0);
    482         touch.setTime(event.getEventTime());
    483         processEvent(touch);
    484     }
    485 
    486     public boolean onFling(MotionEvent event, MotionEvent event2, float vx, float vy) {
    487         TouchEvent touch = getNextFreeTouchEvent();
    488         touch.set(Type.FLING, event.getX(), this.getHeight() - event.getY(), vx, vy);
    489         touch.setPointerId(0);
    490         touch.setTime(event.getEventTime());
    491         processEvent(touch);
    492 
    493         return true;
    494     }
    495 
    496     public boolean onSingleTapConfirmed(MotionEvent event) {
    497         //Nothing to do here the tap has already been detected.
    498         return false;
    499     }
    500 
    501     public boolean onDoubleTap(MotionEvent event) {
    502         TouchEvent touch = getNextFreeTouchEvent();
    503         touch.set(Type.DOUBLETAP, event.getX(), this.getHeight() - event.getY(), 0f, 0f);
    504         touch.setPointerId(0);
    505         touch.setTime(event.getEventTime());
    506         processEvent(touch);
    507         return true;
    508     }
    509 
    510     public boolean onDoubleTapEvent(MotionEvent event) {
    511         return false;
    512     }
    513 
    514     public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) {
    515         TouchEvent touch = getNextFreeTouchEvent();
    516         touch.set(Type.SCALE_START, scaleGestureDetector.getFocusX(), scaleGestureDetector.getFocusY(), 0f, 0f);
    517         touch.setPointerId(0);
    518         touch.setTime(scaleGestureDetector.getEventTime());
    519         touch.setScaleSpan(scaleGestureDetector.getCurrentSpan());
    520         touch.setScaleFactor(scaleGestureDetector.getScaleFactor());
    521         processEvent(touch);
    522         //    System.out.println("scaleBegin");
    523 
    524         return true;
    525     }
    526 
    527     public boolean onScale(ScaleGestureDetector scaleGestureDetector) {
    528         TouchEvent touch = getNextFreeTouchEvent();
    529         touch.set(Type.SCALE_MOVE, scaleGestureDetector.getFocusX(), this.getHeight() - scaleGestureDetector.getFocusY(), 0f, 0f);
    530         touch.setPointerId(0);
    531         touch.setTime(scaleGestureDetector.getEventTime());
    532         touch.setScaleSpan(scaleGestureDetector.getCurrentSpan());
    533         touch.setScaleFactor(scaleGestureDetector.getScaleFactor());
    534         processEvent(touch);
    535         //   System.out.println("scale");
    536 
    537         return false;
    538     }
    539 
    540     public void onScaleEnd(ScaleGestureDetector scaleGestureDetector) {
    541         TouchEvent touch = getNextFreeTouchEvent();
    542         touch.set(Type.SCALE_END, scaleGestureDetector.getFocusX(), this.getHeight() - scaleGestureDetector.getFocusY(), 0f, 0f);
    543         touch.setPointerId(0);
    544         touch.setTime(scaleGestureDetector.getEventTime());
    545         touch.setScaleSpan(scaleGestureDetector.getCurrentSpan());
    546         touch.setScaleFactor(scaleGestureDetector.getScaleFactor());
    547         processEvent(touch);
    548     }
    549 
    550     public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
    551         TouchEvent touch = getNextFreeTouchEvent();
    552         touch.set(Type.SCROLL, e1.getX(), this.getHeight() - e1.getY(), distanceX, distanceY * (-1));
    553         touch.setPointerId(0);
    554         touch.setTime(e1.getEventTime());
    555         processEvent(touch);
    556         //System.out.println("scroll " + e1.getPointerCount());
    557         return false;
    558     }
    559 
    560     public void onShowPress(MotionEvent event) {
    561         TouchEvent touch = getNextFreeTouchEvent();
    562         touch.set(Type.SHOWPRESS, event.getX(), this.getHeight() - event.getY(), 0f, 0f);
    563         touch.setPointerId(0);
    564         touch.setTime(event.getEventTime());
    565         processEvent(touch);
    566     }
    567 
    568     public boolean onSingleTapUp(MotionEvent event) {
    569         TouchEvent touch = getNextFreeTouchEvent();
    570         touch.set(Type.TAP, event.getX(), this.getHeight() - event.getY(), 0f, 0f);
    571         touch.setPointerId(0);
    572         touch.setTime(event.getEventTime());
    573         processEvent(touch);
    574         return true;
    575     }
    576 
    577     @Override
    578     public void setSimulateMouse(boolean simulate) {
    579         mouseEventsEnabled = simulate;
    580     }
    581     @Override
    582     public boolean getSimulateMouse() {
    583         return mouseEventsEnabled;
    584     }
    585 
    586     @Override
    587     public void setSimulateKeyboard(boolean simulate) {
    588         keyboardEventsEnabled = simulate;
    589     }
    590 
    591     @Override
    592     public void setOmitHistoricEvents(boolean dontSendHistory) {
    593         this.dontSendHistory = dontSendHistory;
    594     }
    595 
    596     // TODO: move to TouchInput
    597     public boolean isMouseEventsEnabled() {
    598         return mouseEventsEnabled;
    599     }
    600 
    601     public void setMouseEventsEnabled(boolean mouseEventsEnabled) {
    602         this.mouseEventsEnabled = mouseEventsEnabled;
    603     }
    604 
    605     public boolean isMouseEventsInvertY() {
    606         return mouseEventsInvertY;
    607     }
    608 
    609     public void setMouseEventsInvertY(boolean mouseEventsInvertY) {
    610         this.mouseEventsInvertY = mouseEventsInvertY;
    611     }
    612 
    613     public boolean isMouseEventsInvertX() {
    614         return mouseEventsInvertX;
    615     }
    616 
    617     public void setMouseEventsInvertX(boolean mouseEventsInvertX) {
    618         this.mouseEventsInvertX = mouseEventsInvertX;
    619     }
    620 }
    621