Home | History | Annotate | Download | only in android
      1 /*******************************************************************************
      2  * Copyright 2011 See AUTHORS file.
      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.badlogic.gdx.backends.android;
     18 
     19 import java.util.ArrayList;
     20 import java.util.Arrays;
     21 
     22 import android.app.Activity;
     23 import android.app.AlertDialog;
     24 import android.content.Context;
     25 import android.content.DialogInterface;
     26 import android.content.DialogInterface.OnCancelListener;
     27 import android.hardware.Sensor;
     28 import android.hardware.SensorEvent;
     29 import android.hardware.SensorEventListener;
     30 import android.hardware.SensorManager;
     31 import android.os.Build;
     32 import android.os.Handler;
     33 import android.os.Vibrator;
     34 import android.service.wallpaper.WallpaperService.Engine;
     35 import android.view.MotionEvent;
     36 import android.view.Surface;
     37 import android.view.View;
     38 import android.view.WindowManager;
     39 import android.view.View.OnKeyListener;
     40 import android.view.View.OnTouchListener;
     41 import android.view.inputmethod.InputMethodManager;
     42 import android.widget.EditText;
     43 
     44 import com.badlogic.gdx.Application;
     45 import com.badlogic.gdx.Gdx;
     46 import com.badlogic.gdx.Graphics.DisplayMode;
     47 import com.badlogic.gdx.Input;
     48 import com.badlogic.gdx.Input.TextInputListener;
     49 import com.badlogic.gdx.InputProcessor;
     50 import com.badlogic.gdx.backends.android.AndroidLiveWallpaperService.AndroidWallpaperEngine;
     51 import com.badlogic.gdx.utils.IntSet;
     52 import com.badlogic.gdx.utils.Pool;
     53 
     54 /** An implementation of the {@link Input} interface for Android.
     55  *
     56  * @author mzechner */
     57 /** @author jshapcot */
     58 public class AndroidInput implements Input, OnKeyListener, OnTouchListener {
     59 	static class KeyEvent {
     60 		static final int KEY_DOWN = 0;
     61 		static final int KEY_UP = 1;
     62 		static final int KEY_TYPED = 2;
     63 
     64 		long timeStamp;
     65 		int type;
     66 		int keyCode;
     67 		char keyChar;
     68 	}
     69 
     70 	static class TouchEvent {
     71 		static final int TOUCH_DOWN = 0;
     72 		static final int TOUCH_UP = 1;
     73 		static final int TOUCH_DRAGGED = 2;
     74 		static final int TOUCH_SCROLLED = 3;
     75 		static final int TOUCH_MOVED = 4;
     76 
     77 		long timeStamp;
     78 		int type;
     79 		int x;
     80 		int y;
     81 		int scrollAmount;
     82 		int button;
     83 		int pointer;
     84 	}
     85 
     86 	Pool<KeyEvent> usedKeyEvents = new Pool<KeyEvent>(16, 1000) {
     87 		protected KeyEvent newObject () {
     88 			return new KeyEvent();
     89 		}
     90 	};
     91 
     92 	Pool<TouchEvent> usedTouchEvents = new Pool<TouchEvent>(16, 1000) {
     93 		protected TouchEvent newObject () {
     94 			return new TouchEvent();
     95 		}
     96 	};
     97 
     98 	public static final int NUM_TOUCHES = 20;
     99 	public static final int SUPPORTED_KEYS = 260;
    100 
    101 	ArrayList<OnKeyListener> keyListeners = new ArrayList();
    102 	ArrayList<KeyEvent> keyEvents = new ArrayList();
    103 	ArrayList<TouchEvent> touchEvents = new ArrayList();
    104 	int[] touchX = new int[NUM_TOUCHES];
    105 	int[] touchY = new int[NUM_TOUCHES];
    106 	int[] deltaX = new int[NUM_TOUCHES];
    107 	int[] deltaY = new int[NUM_TOUCHES];
    108 	boolean[] touched = new boolean[NUM_TOUCHES];
    109 	int[] button = new int[NUM_TOUCHES];
    110 	int[] realId = new int[NUM_TOUCHES];
    111 	final boolean hasMultitouch;
    112 	private int keyCount = 0;
    113 	private boolean[] keys = new boolean[SUPPORTED_KEYS];
    114 	private boolean keyJustPressed = false;
    115 	private boolean[] justPressedKeys = new boolean[SUPPORTED_KEYS];
    116 	private SensorManager manager;
    117 	public boolean accelerometerAvailable = false;
    118 	private final float[] accelerometerValues = new float[3];
    119 	public boolean gyroscopeAvailable = false;
    120 	private final float[] gyroscopeValues = new float[3];
    121 	private String text = null;
    122 	private TextInputListener textListener = null;
    123 	private Handler handle;
    124 	final Application app;
    125 	final Context context;
    126 	private final AndroidTouchHandler touchHandler;
    127 	private int sleepTime = 0;
    128 	private boolean catchBack = false;
    129 	private boolean catchMenu = false;
    130 	protected final Vibrator vibrator;
    131 	private boolean compassAvailable = false;
    132 	boolean keyboardAvailable;
    133 	private final float[] magneticFieldValues = new float[3];
    134 	private float azimuth = 0;
    135 	private float pitch = 0;
    136 	private float roll = 0;
    137 	private float inclination = 0;
    138 	private boolean justTouched = false;
    139 	private InputProcessor processor;
    140 	private final AndroidApplicationConfiguration config;
    141 	private final Orientation nativeOrientation;
    142 	private long currentEventTimeStamp = System.nanoTime();
    143 	private final AndroidOnscreenKeyboard onscreenKeyboard;
    144 
    145 	private SensorEventListener accelerometerListener;
    146 	private SensorEventListener gyroscopeListener;
    147 	private SensorEventListener compassListener;
    148 
    149 	public AndroidInput (Application activity, Context context, Object view, AndroidApplicationConfiguration config) {
    150 		// we hook into View, for LWPs we call onTouch below directly from
    151 		// within the AndroidLivewallpaperEngine#onTouchEvent() method.
    152 		if (view instanceof View) {
    153 			View v = (View)view;
    154 			v.setOnKeyListener(this);
    155 			v.setOnTouchListener(this);
    156 			v.setFocusable(true);
    157 			v.setFocusableInTouchMode(true);
    158 			v.requestFocus();
    159 		}
    160 		this.config = config;
    161 		this.onscreenKeyboard = new AndroidOnscreenKeyboard(context, new Handler(), this);
    162 
    163 		for (int i = 0; i < realId.length; i++)
    164 			realId[i] = -1;
    165 		handle = new Handler();
    166 		this.app = activity;
    167 		this.context = context;
    168 		this.sleepTime = config.touchSleepTime;
    169 		touchHandler = new AndroidMultiTouchHandler();
    170 		hasMultitouch = touchHandler.supportsMultitouch(context);
    171 
    172 		vibrator = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);
    173 
    174 		int rotation = getRotation();
    175 		DisplayMode mode = app.getGraphics().getDisplayMode();
    176 		if (((rotation == 0 || rotation == 180) && (mode.width >= mode.height))
    177 			|| ((rotation == 90 || rotation == 270) && (mode.width <= mode.height))) {
    178 			nativeOrientation = Orientation.Landscape;
    179 		} else {
    180 			nativeOrientation = Orientation.Portrait;
    181 		}
    182 	}
    183 
    184 	@Override
    185 	public float getAccelerometerX () {
    186 		return accelerometerValues[0];
    187 	}
    188 
    189 	@Override
    190 	public float getAccelerometerY () {
    191 		return accelerometerValues[1];
    192 	}
    193 
    194 	@Override
    195 	public float getAccelerometerZ () {
    196 		return accelerometerValues[2];
    197 	}
    198 
    199 	@Override
    200 	public float getGyroscopeX () {
    201 		return gyroscopeValues[0];
    202 	}
    203 
    204 	@Override
    205 	public float getGyroscopeY () {
    206 		return gyroscopeValues[1];
    207 	}
    208 
    209 	@Override
    210 	public float getGyroscopeZ () {
    211 		return gyroscopeValues[2];
    212 	}
    213 
    214 	@Override
    215 	public void getTextInput (final TextInputListener listener, final String title, final String text, final String hint) {
    216 		handle.post(new Runnable() {
    217 			public void run () {
    218 				AlertDialog.Builder alert = new AlertDialog.Builder(context);
    219 				alert.setTitle(title);
    220 				final EditText input = new EditText(context);
    221 				input.setHint(hint);
    222 				input.setText(text);
    223 				input.setSingleLine();
    224 				alert.setView(input);
    225 				alert.setPositiveButton(context.getString(android.R.string.ok), new DialogInterface.OnClickListener() {
    226 					public void onClick (DialogInterface dialog, int whichButton) {
    227 						Gdx.app.postRunnable(new Runnable() {
    228 							@Override
    229 							public void run () {
    230 								listener.input(input.getText().toString());
    231 							}
    232 						});
    233 					}
    234 				});
    235 				alert.setNegativeButton(context.getString(android.R.string.cancel), new DialogInterface.OnClickListener() {
    236 					public void onClick (DialogInterface dialog, int whichButton) {
    237 						Gdx.app.postRunnable(new Runnable() {
    238 							@Override
    239 							public void run () {
    240 								listener.canceled();
    241 							}
    242 						});
    243 					}
    244 				});
    245 				alert.setOnCancelListener(new OnCancelListener() {
    246 					@Override
    247 					public void onCancel (DialogInterface arg0) {
    248 						Gdx.app.postRunnable(new Runnable() {
    249 							@Override
    250 							public void run () {
    251 								listener.canceled();
    252 							}
    253 						});
    254 					}
    255 				});
    256 				alert.show();
    257 			}
    258 		});
    259 	}
    260 
    261 	@Override
    262 	public int getX () {
    263 		synchronized (this) {
    264 			return touchX[0];
    265 		}
    266 	}
    267 
    268 	@Override
    269 	public int getY () {
    270 		synchronized (this) {
    271 			return touchY[0];
    272 		}
    273 	}
    274 
    275 	@Override
    276 	public int getX (int pointer) {
    277 		synchronized (this) {
    278 			return touchX[pointer];
    279 		}
    280 	}
    281 
    282 	@Override
    283 	public int getY (int pointer) {
    284 		synchronized (this) {
    285 			return touchY[pointer];
    286 		}
    287 	}
    288 
    289 	public boolean isTouched (int pointer) {
    290 		synchronized (this) {
    291 			return touched[pointer];
    292 		}
    293 	}
    294 
    295 	@Override
    296 	public synchronized boolean isKeyPressed (int key) {
    297 		if (key == Input.Keys.ANY_KEY) {
    298 			return keyCount > 0;
    299 		}
    300 		if (key < 0 || key >= SUPPORTED_KEYS) {
    301 			return false;
    302 		}
    303 		return keys[key];
    304 	}
    305 
    306 	@Override
    307 	public synchronized boolean isKeyJustPressed (int key) {
    308 		if (key == Input.Keys.ANY_KEY) {
    309 			return keyJustPressed;
    310 		}
    311 		if (key < 0 || key >= SUPPORTED_KEYS) {
    312 			return false;
    313 		}
    314 		return justPressedKeys[key];
    315 	}
    316 
    317 	@Override
    318 	public boolean isTouched () {
    319 		synchronized (this) {
    320 			if (hasMultitouch) {
    321 				for (int pointer = 0; pointer < NUM_TOUCHES; pointer++) {
    322 					if (touched[pointer]) {
    323 						return true;
    324 					}
    325 				}
    326 			}
    327 			return touched[0];
    328 		}
    329 	}
    330 
    331 	public void setInputProcessor (InputProcessor processor) {
    332 		synchronized (this) {
    333 			this.processor = processor;
    334 		}
    335 	}
    336 
    337 	void processEvents () {
    338 		synchronized (this) {
    339 			justTouched = false;
    340 			if (keyJustPressed) {
    341 				keyJustPressed = false;
    342 				for (int i = 0; i < justPressedKeys.length; i++) {
    343 					justPressedKeys[i] = false;
    344 				}
    345 			}
    346 
    347 			if (processor != null) {
    348 				final InputProcessor processor = this.processor;
    349 
    350 				int len = keyEvents.size();
    351 				for (int i = 0; i < len; i++) {
    352 					KeyEvent e = keyEvents.get(i);
    353 					currentEventTimeStamp = e.timeStamp;
    354 					switch (e.type) {
    355 					case KeyEvent.KEY_DOWN:
    356 						processor.keyDown(e.keyCode);
    357 						keyJustPressed = true;
    358 						justPressedKeys[e.keyCode] = true;
    359 						break;
    360 					case KeyEvent.KEY_UP:
    361 						processor.keyUp(e.keyCode);
    362 						break;
    363 					case KeyEvent.KEY_TYPED:
    364 						processor.keyTyped(e.keyChar);
    365 					}
    366 					usedKeyEvents.free(e);
    367 				}
    368 
    369 				len = touchEvents.size();
    370 				for (int i = 0; i < len; i++) {
    371 					TouchEvent e = touchEvents.get(i);
    372 					currentEventTimeStamp = e.timeStamp;
    373 					switch (e.type) {
    374 					case TouchEvent.TOUCH_DOWN:
    375 						processor.touchDown(e.x, e.y, e.pointer, e.button);
    376 						justTouched = true;
    377 						break;
    378 					case TouchEvent.TOUCH_UP:
    379 						processor.touchUp(e.x, e.y, e.pointer, e.button);
    380 						break;
    381 					case TouchEvent.TOUCH_DRAGGED:
    382 						processor.touchDragged(e.x, e.y, e.pointer);
    383 						break;
    384 					case TouchEvent.TOUCH_MOVED:
    385 						processor.mouseMoved(e.x, e.y);
    386 						break;
    387 					case TouchEvent.TOUCH_SCROLLED:
    388 						processor.scrolled(e.scrollAmount);
    389 					}
    390 					usedTouchEvents.free(e);
    391 				}
    392 			} else {
    393 				int len = touchEvents.size();
    394 				for (int i = 0; i < len; i++) {
    395 					TouchEvent e = touchEvents.get(i);
    396 					if (e.type == TouchEvent.TOUCH_DOWN) justTouched = true;
    397 					usedTouchEvents.free(e);
    398 				}
    399 
    400 				len = keyEvents.size();
    401 				for (int i = 0; i < len; i++) {
    402 					usedKeyEvents.free(keyEvents.get(i));
    403 				}
    404 			}
    405 
    406 			if (touchEvents.size() == 0) {
    407 				for (int i = 0; i < deltaX.length; i++) {
    408 					deltaX[0] = 0;
    409 					deltaY[0] = 0;
    410 				}
    411 			}
    412 
    413 			keyEvents.clear();
    414 			touchEvents.clear();
    415 		}
    416 	}
    417 
    418 	boolean requestFocus = true;
    419 
    420 	@Override
    421 	public boolean onTouch (View view, MotionEvent event) {
    422 		if (requestFocus && view != null) {
    423 			view.setFocusableInTouchMode(true);
    424 			view.requestFocus();
    425 			requestFocus = false;
    426 		}
    427 
    428 		// synchronized in handler.postTouchEvent()
    429 		touchHandler.onTouch(event, this);
    430 
    431 		if (sleepTime != 0) {
    432 			try {
    433 				Thread.sleep(sleepTime);
    434 			} catch (InterruptedException e) {
    435 			}
    436 		}
    437 		return true;
    438 	}
    439 
    440 	/** Called in {@link AndroidLiveWallpaperService} on tap
    441 	 * @param x
    442 	 * @param y */
    443 	public void onTap (int x, int y) {
    444 		postTap(x, y);
    445 	}
    446 
    447 	/** Called in {@link AndroidLiveWallpaperService} on drop
    448 	 * @param x
    449 	 * @param y */
    450 	public void onDrop (int x, int y) {
    451 		postTap(x, y);
    452 	}
    453 
    454 	protected void postTap (int x, int y) {
    455 		synchronized (this) {
    456 			TouchEvent event = usedTouchEvents.obtain();
    457 			event.timeStamp = System.nanoTime();
    458 			event.pointer = 0;
    459 			event.x = x;
    460 			event.y = y;
    461 			event.type = TouchEvent.TOUCH_DOWN;
    462 			touchEvents.add(event);
    463 
    464 			event = usedTouchEvents.obtain();
    465 			event.timeStamp = System.nanoTime();
    466 			event.pointer = 0;
    467 			event.x = x;
    468 			event.y = y;
    469 			event.type = TouchEvent.TOUCH_UP;
    470 			touchEvents.add(event);
    471 		}
    472 		Gdx.app.getGraphics().requestRendering();
    473 	}
    474 
    475 	@Override
    476 	public boolean onKey (View v, int keyCode, android.view.KeyEvent e) {
    477 		for (int i = 0, n = keyListeners.size(); i < n; i++)
    478 			if (keyListeners.get(i).onKey(v, keyCode, e)) return true;
    479 
    480 		synchronized (this) {
    481 			KeyEvent event = null;
    482 
    483 			if (e.getKeyCode() == android.view.KeyEvent.KEYCODE_UNKNOWN && e.getAction() == android.view.KeyEvent.ACTION_MULTIPLE) {
    484 				String chars = e.getCharacters();
    485 				for (int i = 0; i < chars.length(); i++) {
    486 					event = usedKeyEvents.obtain();
    487 					event.timeStamp = System.nanoTime();
    488 					event.keyCode = 0;
    489 					event.keyChar = chars.charAt(i);
    490 					event.type = KeyEvent.KEY_TYPED;
    491 					keyEvents.add(event);
    492 				}
    493 				return false;
    494 			}
    495 
    496 			char character = (char)e.getUnicodeChar();
    497 			// Android doesn't report a unicode char for back space. hrm...
    498 			if (keyCode == 67) character = '\b';
    499 			if (e.getKeyCode() < 0 || e.getKeyCode() >= SUPPORTED_KEYS) {
    500 				return false;
    501 			}
    502 
    503 			switch (e.getAction()) {
    504 			case android.view.KeyEvent.ACTION_DOWN:
    505 				event = usedKeyEvents.obtain();
    506 				event.timeStamp = System.nanoTime();
    507 				event.keyChar = 0;
    508 				event.keyCode = e.getKeyCode();
    509 				event.type = KeyEvent.KEY_DOWN;
    510 
    511 				// Xperia hack for circle key. gah...
    512 				if (keyCode == android.view.KeyEvent.KEYCODE_BACK && e.isAltPressed()) {
    513 					keyCode = Keys.BUTTON_CIRCLE;
    514 					event.keyCode = keyCode;
    515 				}
    516 
    517 				keyEvents.add(event);
    518 				if (!keys[event.keyCode]) {
    519 					keyCount++;
    520 					keys[event.keyCode] = true;
    521 				}
    522 				break;
    523 			case android.view.KeyEvent.ACTION_UP:
    524 				long timeStamp = System.nanoTime();
    525 				event = usedKeyEvents.obtain();
    526 				event.timeStamp = timeStamp;
    527 				event.keyChar = 0;
    528 				event.keyCode = e.getKeyCode();
    529 				event.type = KeyEvent.KEY_UP;
    530 				// Xperia hack for circle key. gah...
    531 				if (keyCode == android.view.KeyEvent.KEYCODE_BACK && e.isAltPressed()) {
    532 					keyCode = Keys.BUTTON_CIRCLE;
    533 					event.keyCode = keyCode;
    534 				}
    535 				keyEvents.add(event);
    536 
    537 				event = usedKeyEvents.obtain();
    538 				event.timeStamp = timeStamp;
    539 				event.keyChar = character;
    540 				event.keyCode = 0;
    541 				event.type = KeyEvent.KEY_TYPED;
    542 				keyEvents.add(event);
    543 
    544 				if (keyCode == Keys.BUTTON_CIRCLE) {
    545 					if (keys[Keys.BUTTON_CIRCLE]) {
    546 						keyCount--;
    547 						keys[Keys.BUTTON_CIRCLE] = false;
    548 					}
    549 				} else {
    550 					if (keys[e.getKeyCode()]) {
    551 						keyCount--;
    552 						keys[e.getKeyCode()] = false;
    553 					}
    554 				}
    555 			}
    556 			app.getGraphics().requestRendering();
    557 		}
    558 
    559 		// circle button on Xperia Play shouldn't need catchBack == true
    560 		if (keyCode == Keys.BUTTON_CIRCLE) return true;
    561 		if (catchBack && keyCode == android.view.KeyEvent.KEYCODE_BACK) return true;
    562 		if (catchMenu && keyCode == android.view.KeyEvent.KEYCODE_MENU) return true;
    563 		return false;
    564 	}
    565 
    566 	@Override
    567 	public void setOnscreenKeyboardVisible (final boolean visible) {
    568 		handle.post(new Runnable() {
    569 			public void run () {
    570 				InputMethodManager manager = (InputMethodManager)context.getSystemService(Context.INPUT_METHOD_SERVICE);
    571 				if (visible) {
    572 					View view = ((AndroidGraphics)app.getGraphics()).getView();
    573 					view.setFocusable(true);
    574 					view.setFocusableInTouchMode(true);
    575 					manager.showSoftInput(((AndroidGraphics)app.getGraphics()).getView(), 0);
    576 				} else {
    577 					manager.hideSoftInputFromWindow(((AndroidGraphics)app.getGraphics()).getView().getWindowToken(), 0);
    578 				}
    579 			}
    580 		});
    581 	}
    582 
    583 	@Override
    584 	public void setCatchBackKey (boolean catchBack) {
    585 		this.catchBack = catchBack;
    586 	}
    587 
    588 	@Override
    589 	public boolean isCatchBackKey() {
    590 		return catchBack;
    591 	}
    592 
    593 	@Override
    594 	public void setCatchMenuKey (boolean catchMenu) {
    595 		this.catchMenu = catchMenu;
    596 	}
    597 
    598 	@Override
    599 	public boolean isCatchMenuKey () {
    600 		return catchMenu;
    601 	}
    602 
    603 	@Override
    604 	public void vibrate (int milliseconds) {
    605 		vibrator.vibrate(milliseconds);
    606 	}
    607 
    608 	@Override
    609 	public void vibrate (long[] pattern, int repeat) {
    610 		vibrator.vibrate(pattern, repeat);
    611 	}
    612 
    613 	@Override
    614 	public void cancelVibrate () {
    615 		vibrator.cancel();
    616 	}
    617 
    618 	@Override
    619 	public boolean justTouched () {
    620 		return justTouched;
    621 	}
    622 
    623 	@Override
    624 	public boolean isButtonPressed (int button) {
    625 		synchronized (this) {
    626 			if (hasMultitouch) {
    627 				for (int pointer = 0; pointer < NUM_TOUCHES; pointer++) {
    628 					if (touched[pointer] && (this.button[pointer] == button)) {
    629 						return true;
    630 					}
    631 				}
    632 			}
    633 			return (touched[0] && (this.button[0] == button));
    634 		}
    635 	}
    636 
    637 	final float[] R = new float[9];
    638 	final float[] orientation = new float[3];
    639 
    640 	private void updateOrientation () {
    641 		if (SensorManager.getRotationMatrix(R, null, accelerometerValues, magneticFieldValues)) {
    642 			SensorManager.getOrientation(R, orientation);
    643 			azimuth = (float)Math.toDegrees(orientation[0]);
    644 			pitch = (float)Math.toDegrees(orientation[1]);
    645 			roll = (float)Math.toDegrees(orientation[2]);
    646 		}
    647 	}
    648 
    649 	/** Returns the rotation matrix describing the devices rotation as per <a href=
    650 	 * "http://developer.android.com/reference/android/hardware/SensorManager.html#getRotationMatrix(float[], float[], float[], float[])"
    651 	 * >SensorManager#getRotationMatrix(float[], float[], float[], float[])</a>. Does not manipulate the matrix if the platform
    652 	 * does not have an accelerometer.
    653 	 * @param matrix */
    654 	public void getRotationMatrix (float[] matrix) {
    655 		SensorManager.getRotationMatrix(matrix, null, accelerometerValues, magneticFieldValues);
    656 	}
    657 
    658 	@Override
    659 	public float getAzimuth () {
    660 		if (!compassAvailable) return 0;
    661 
    662 		updateOrientation();
    663 		return azimuth;
    664 	}
    665 
    666 	@Override
    667 	public float getPitch () {
    668 		if (!compassAvailable) return 0;
    669 
    670 		updateOrientation();
    671 		return pitch;
    672 	}
    673 
    674 	@Override
    675 	public float getRoll () {
    676 		if (!compassAvailable) return 0;
    677 
    678 		updateOrientation();
    679 		return roll;
    680 	}
    681 
    682 	void registerSensorListeners () {
    683 		if (config.useAccelerometer) {
    684 			manager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
    685 			if (manager.getSensorList(Sensor.TYPE_ACCELEROMETER).size() == 0) {
    686 				accelerometerAvailable = false;
    687 			} else {
    688 				Sensor accelerometer = manager.getSensorList(Sensor.TYPE_ACCELEROMETER).get(0);
    689 				accelerometerListener = new SensorListener(this.nativeOrientation, this.accelerometerValues, this.magneticFieldValues, this.gyroscopeValues);
    690 				accelerometerAvailable = manager.registerListener(accelerometerListener, accelerometer,
    691 					SensorManager.SENSOR_DELAY_GAME);
    692 			}
    693 		} else
    694 			accelerometerAvailable = false;
    695 
    696 		if (config.useGyroscope) {
    697 			manager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
    698 			if (manager.getSensorList(Sensor.TYPE_GYROSCOPE).size() == 0) {
    699 				gyroscopeAvailable = false;
    700 			} else {
    701 				Sensor gyroscope = manager.getSensorList(Sensor.TYPE_GYROSCOPE).get(0);
    702 				gyroscopeListener = new SensorListener(this.nativeOrientation, this.gyroscopeValues, this.magneticFieldValues, this.gyroscopeValues);
    703 				gyroscopeAvailable = manager.registerListener(gyroscopeListener, gyroscope,
    704 					SensorManager.SENSOR_DELAY_GAME);
    705 			}
    706 		} else
    707 			gyroscopeAvailable = false;
    708 
    709 		if (config.useCompass) {
    710 			if (manager == null) manager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
    711 			Sensor sensor = manager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
    712 			if (sensor != null) {
    713 				compassAvailable = accelerometerAvailable;
    714 				if (compassAvailable) {
    715 					compassListener = new SensorListener(this.nativeOrientation, this.accelerometerValues, this.magneticFieldValues, this.gyroscopeValues);
    716 					compassAvailable = manager.registerListener(compassListener, sensor, SensorManager.SENSOR_DELAY_GAME);
    717 				}
    718 			} else {
    719 				compassAvailable = false;
    720 			}
    721 		} else
    722 			compassAvailable = false;
    723 		Gdx.app.log("AndroidInput", "sensor listener setup");
    724 	}
    725 
    726 	void unregisterSensorListeners () {
    727 		if (manager != null) {
    728 			if (accelerometerListener != null) {
    729 				manager.unregisterListener(accelerometerListener);
    730 				accelerometerListener = null;
    731 			}
    732 			if (gyroscopeListener != null) {
    733 				manager.unregisterListener(gyroscopeListener);
    734 				gyroscopeListener = null;
    735 			}
    736 			if (compassListener != null) {
    737 				manager.unregisterListener(compassListener);
    738 				compassListener = null;
    739 			}
    740 			manager = null;
    741 		}
    742 		Gdx.app.log("AndroidInput", "sensor listener tear down");
    743 	}
    744 
    745 	@Override
    746 	public InputProcessor getInputProcessor () {
    747 		return this.processor;
    748 	}
    749 
    750 	@Override
    751 	public boolean isPeripheralAvailable (Peripheral peripheral) {
    752 		if (peripheral == Peripheral.Accelerometer) return accelerometerAvailable;
    753 		if (peripheral == Peripheral.Gyroscope) return gyroscopeAvailable;
    754 		if (peripheral == Peripheral.Compass) return compassAvailable;
    755 		if (peripheral == Peripheral.HardwareKeyboard) return keyboardAvailable;
    756 		if (peripheral == Peripheral.OnscreenKeyboard) return true;
    757 		if (peripheral == Peripheral.Vibrator)
    758 			return (Build.VERSION.SDK_INT >= 11 && vibrator != null) ? vibrator.hasVibrator() : vibrator != null;
    759 		if (peripheral == Peripheral.MultitouchScreen) return hasMultitouch;
    760 		return false;
    761 	}
    762 
    763 	public int getFreePointerIndex () {
    764 		int len = realId.length;
    765 		for (int i = 0; i < len; i++) {
    766 			if (realId[i] == -1) return i;
    767 		}
    768 
    769 		realId = resize(realId);
    770 		touchX = resize(touchX);
    771 		touchY = resize(touchY);
    772 		deltaX = resize(deltaX);
    773 		deltaY = resize(deltaY);
    774 		touched = resize(touched);
    775 		button = resize(button);
    776 
    777 		return len;
    778 	}
    779 
    780 	private int[] resize (int[] orig) {
    781 		int[] tmp = new int[orig.length + 2];
    782 		System.arraycopy(orig, 0, tmp, 0, orig.length);
    783 		return tmp;
    784 	}
    785 
    786 	private boolean[] resize (boolean[] orig) {
    787 		boolean[] tmp = new boolean[orig.length + 2];
    788 		System.arraycopy(orig, 0, tmp, 0, orig.length);
    789 		return tmp;
    790 	}
    791 
    792 	public int lookUpPointerIndex (int pointerId) {
    793 		int len = realId.length;
    794 		for (int i = 0; i < len; i++) {
    795 			if (realId[i] == pointerId) return i;
    796 		}
    797 
    798 		StringBuffer buf = new StringBuffer();
    799 		for (int i = 0; i < len; i++) {
    800 			buf.append(i + ":" + realId[i] + " ");
    801 		}
    802 		Gdx.app.log("AndroidInput", "Pointer ID lookup failed: " + pointerId + ", " + buf.toString());
    803 		return -1;
    804 	}
    805 
    806 	@Override
    807 	public int getRotation () {
    808 		int orientation = 0;
    809 
    810 		if (context instanceof Activity) {
    811 			orientation = ((Activity)context).getWindowManager().getDefaultDisplay().getRotation();
    812 		} else {
    813 			orientation = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getRotation();
    814 		}
    815 
    816 		switch (orientation) {
    817 		case Surface.ROTATION_0:
    818 			return 0;
    819 		case Surface.ROTATION_90:
    820 			return 90;
    821 		case Surface.ROTATION_180:
    822 			return 180;
    823 		case Surface.ROTATION_270:
    824 			return 270;
    825 		default:
    826 			return 0;
    827 		}
    828 	}
    829 
    830 	@Override
    831 	public Orientation getNativeOrientation () {
    832 		return nativeOrientation;
    833 	}
    834 
    835 	@Override
    836 	public void setCursorCatched (boolean catched) {
    837 	}
    838 
    839 	@Override
    840 	public boolean isCursorCatched () {
    841 		return false;
    842 	}
    843 
    844 	@Override
    845 	public int getDeltaX () {
    846 		return deltaX[0];
    847 	}
    848 
    849 	@Override
    850 	public int getDeltaX (int pointer) {
    851 		return deltaX[pointer];
    852 	}
    853 
    854 	@Override
    855 	public int getDeltaY () {
    856 		return deltaY[0];
    857 	}
    858 
    859 	@Override
    860 	public int getDeltaY (int pointer) {
    861 		return deltaY[pointer];
    862 	}
    863 
    864 	@Override
    865 	public void setCursorPosition (int x, int y) {
    866 	}
    867 
    868 	@Override
    869 	public long getCurrentEventTime () {
    870 		return currentEventTimeStamp;
    871 	}
    872 
    873 	public void addKeyListener (OnKeyListener listener) {
    874 		keyListeners.add(listener);
    875 	}
    876 
    877 	public void onPause () {
    878 		unregisterSensorListeners();
    879 
    880 		// erase pointer ids. this sucks donkeyballs...
    881 		Arrays.fill(realId, -1);
    882 
    883 		// erase touched state. this also sucks donkeyballs...
    884 		Arrays.fill(touched, false);
    885 	}
    886 
    887 	public void onResume () {
    888 		registerSensorListeners();
    889 	}
    890 
    891 	/** Our implementation of SensorEventListener. Because Android doesn't like it when we register more than one Sensor to a single
    892 	 * SensorEventListener, we add one of these for each Sensor. Could use an anonymous class, but I don't see any harm in
    893 	 * explicitly defining it here. Correct me if I am wrong. */
    894 	private class SensorListener implements SensorEventListener {
    895 		final float[] accelerometerValues;
    896 		final float[] magneticFieldValues;
    897 		final Orientation nativeOrientation;
    898 		final float[] gyroscopeValues;
    899 
    900 		SensorListener (Orientation nativeOrientation, float[] accelerometerValues, float[] magneticFieldValues, float[] gyroscopeValues) {
    901 			this.accelerometerValues = accelerometerValues;
    902 			this.magneticFieldValues = magneticFieldValues;
    903 			this.nativeOrientation = nativeOrientation;
    904 			this.gyroscopeValues = gyroscopeValues;
    905 		}
    906 
    907 		@Override
    908 		public void onAccuracyChanged (Sensor arg0, int arg1) {
    909 
    910 		}
    911 
    912 		@Override
    913 		public void onSensorChanged (SensorEvent event) {
    914 			if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
    915 				if (nativeOrientation == Orientation.Portrait) {
    916 					System.arraycopy(event.values, 0, accelerometerValues, 0, accelerometerValues.length);
    917 				} else {
    918 					accelerometerValues[0] = event.values[1];
    919 					accelerometerValues[1] = -event.values[0];
    920 					accelerometerValues[2] = event.values[2];
    921 				}
    922 			}
    923 			if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
    924 				System.arraycopy(event.values, 0, magneticFieldValues, 0, magneticFieldValues.length);
    925 			}
    926 			if (event.sensor.getType() == Sensor.TYPE_GYROSCOPE) {
    927 				if (nativeOrientation == Orientation.Portrait) {
    928 					System.arraycopy(event.values, 0, gyroscopeValues, 0, gyroscopeValues.length);
    929 				} else {
    930 					gyroscopeValues[0] = event.values[1];
    931 					gyroscopeValues[1] = -event.values[0];
    932 					gyroscopeValues[2] = event.values[2];
    933 				}
    934 			}
    935 		}
    936 	}
    937 }
    938