1package com.jme3.input.android;
2
3import android.content.Context;
4import android.opengl.GLSurfaceView;
5import android.util.AttributeSet;
6import android.view.GestureDetector;
7import android.view.KeyEvent;
8import android.view.MotionEvent;
9import android.view.ScaleGestureDetector;
10import com.jme3.input.KeyInput;
11import com.jme3.input.RawInputListener;
12import com.jme3.input.TouchInput;
13import com.jme3.input.event.MouseButtonEvent;
14import com.jme3.input.event.MouseMotionEvent;
15import com.jme3.input.event.TouchEvent;
16import com.jme3.input.event.TouchEvent.Type;
17import com.jme3.math.Vector2f;
18import com.jme3.util.RingBuffer;
19import java.util.HashMap;
20import 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 */
27public 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