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