package org.andengine.engine; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; import org.andengine.BuildConfig; import org.andengine.audio.music.MusicFactory; import org.andengine.audio.music.MusicManager; import org.andengine.audio.sound.SoundFactory; import org.andengine.audio.sound.SoundManager; import org.andengine.engine.camera.Camera; import org.andengine.engine.handler.DrawHandlerList; import org.andengine.engine.handler.IDrawHandler; import org.andengine.engine.handler.IUpdateHandler; import org.andengine.engine.handler.UpdateHandlerList; import org.andengine.engine.handler.runnable.RunnableHandler; import org.andengine.engine.options.EngineOptions; import org.andengine.entity.scene.Scene; import org.andengine.input.sensor.SensorDelay; import org.andengine.input.sensor.acceleration.AccelerationData; import org.andengine.input.sensor.acceleration.AccelerationSensorOptions; import org.andengine.input.sensor.acceleration.IAccelerationListener; import org.andengine.input.sensor.location.ILocationListener; import org.andengine.input.sensor.location.LocationProviderStatus; import org.andengine.input.sensor.location.LocationSensorOptions; import org.andengine.input.sensor.orientation.IOrientationListener; import org.andengine.input.sensor.orientation.OrientationData; import org.andengine.input.sensor.orientation.OrientationSensorOptions; import org.andengine.input.touch.TouchEvent; import org.andengine.input.touch.controller.ITouchController; import org.andengine.input.touch.controller.ITouchEventCallback; import org.andengine.input.touch.controller.MultiTouchController; import org.andengine.input.touch.controller.SingleTouchController; import org.andengine.opengl.font.FontFactory; import org.andengine.opengl.font.FontManager; import org.andengine.opengl.shader.ShaderProgramManager; import org.andengine.opengl.texture.TextureManager; import org.andengine.opengl.texture.atlas.bitmap.BitmapTextureAtlasTextureRegionFactory; import org.andengine.opengl.util.GLState; import org.andengine.opengl.vbo.VertexBufferObjectManager; import org.andengine.util.debug.Debug; import org.andengine.util.time.TimeConstants; import android.content.Context; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.location.LocationProvider; import android.os.Bundle; import android.os.Vibrator; import android.view.Display; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.WindowManager; /** * (c) 2010 Nicolas Gramlich * (c) 2011 Zynga Inc. * * @author Nicolas Gramlich * @since 12:21:31 - 08.03.2010 */ public class Engine implements SensorEventListener, OnTouchListener, ITouchEventCallback, LocationListener { // =========================================================== // Constants // =========================================================== private static final SensorDelay SENSORDELAY_DEFAULT = SensorDelay.GAME; private static final int UPDATEHANDLERS_CAPACITY_DEFAULT = 8; private static final int DRAWHANDLERS_CAPACITY_DEFAULT = 4; // =========================================================== // Fields // =========================================================== private boolean mRunning; private boolean mDestroyed; private long mLastTick; private float mSecondsElapsedTotal; private final EngineLock mEngineLock; private final UpdateThread mUpdateThread; private final RunnableHandler mUpdateThreadRunnableHandler = new RunnableHandler(); private final EngineOptions mEngineOptions; protected final Camera mCamera; private ITouchController mTouchController; private final VertexBufferObjectManager mVertexBufferObjectManager = new VertexBufferObjectManager(); private final TextureManager mTextureManager = new TextureManager(); private final FontManager mFontManager = new FontManager(); private final ShaderProgramManager mShaderProgramManager = new ShaderProgramManager(); private final SoundManager mSoundManager; private final MusicManager mMusicManager; protected Scene mScene; private Vibrator mVibrator; private ILocationListener mLocationListener; private Location mLocation; private IAccelerationListener mAccelerationListener; private AccelerationData mAccelerationData; private IOrientationListener mOrientationListener; private OrientationData mOrientationData; private final UpdateHandlerList mUpdateHandlers = new UpdateHandlerList(Engine.UPDATEHANDLERS_CAPACITY_DEFAULT); private final DrawHandlerList mDrawHandlers = new DrawHandlerList(Engine.DRAWHANDLERS_CAPACITY_DEFAULT); protected int mSurfaceWidth = 1; // 1 to prevent accidental DIV/0 protected int mSurfaceHeight = 1; // 1 to prevent accidental DIV/0 // =========================================================== // Constructors // =========================================================== public Engine(final EngineOptions pEngineOptions) { /* Initialize Factory and Manager classes. */ BitmapTextureAtlasTextureRegionFactory.reset(); SoundFactory.onCreate(); MusicFactory.onCreate(); FontFactory.onCreate(); this.mVertexBufferObjectManager.onCreate(); this.mTextureManager.onCreate(); this.mFontManager.onCreate(); this.mShaderProgramManager.onCreate(); /* Apply EngineOptions. */ this.mEngineOptions = pEngineOptions; if(this.mEngineOptions.hasEngineLock()) { this.mEngineLock = pEngineOptions.getEngineLock(); } else { this.mEngineLock = new EngineLock(false); } this.mCamera = pEngineOptions.getCamera(); /* Touch. */ if(this.mEngineOptions.getTouchOptions().needsMultiTouch()) { this.setTouchController(new MultiTouchController()); } else { this.setTouchController(new SingleTouchController()); } /* Audio. */ if(this.mEngineOptions.getAudioOptions().needsSound()) { this.mSoundManager = new SoundManager(this.mEngineOptions.getAudioOptions().getSoundOptions().getMaxSimultaneousStreams()); } else { this.mSoundManager = null; } if(this.mEngineOptions.getAudioOptions().needsMusic()) { this.mMusicManager = new MusicManager(); } else { this.mMusicManager = null; } /* Start the UpdateThread. */ if(this.mEngineOptions.hasUpdateThread()) { this.mUpdateThread = this.mEngineOptions.getUpdateThread(); } else { this.mUpdateThread = new UpdateThread(); } this.mUpdateThread.setEngine(this); } public void startUpdateThread() throws IllegalThreadStateException { this.mUpdateThread.start(); } // =========================================================== // Getter & Setter // =========================================================== public synchronized boolean isRunning() { return this.mRunning; } public synchronized void start() { if(!this.mRunning) { this.mLastTick = System.nanoTime(); this.mRunning = true; } } public synchronized void stop() { if(this.mRunning) { this.mRunning = false; } } /** * The {@link EngineLock} can be used to {@link EngineLock#lock()}/{@link EngineLock#unlock()} on, to ensure the code in between runs mutually exclusive to the {@link UpdateThread} and the GL{@link Thread}. * When the caller already is on the {@link UpdateThread} or the GL-{@link Thread}, that code is executed immediately. * @return the {@link EngineLock} the {@link Engine} locks on to ensure mutually exclusivity to the {@link UpdateThread} and the GL{@link Thread}. */ public EngineLock getEngineLock() { return this.mEngineLock; } public Scene getScene() { return this.mScene; } public void setScene(final Scene pScene) { this.mScene = pScene; } public EngineOptions getEngineOptions() { return this.mEngineOptions; } public Camera getCamera() { return this.mCamera; } public float getSecondsElapsedTotal() { return this.mSecondsElapsedTotal; } public void setSurfaceSize(final int pSurfaceWidth, final int pSurfaceHeight) { this.mSurfaceWidth = pSurfaceWidth; this.mSurfaceHeight = pSurfaceHeight; this.onUpdateCameraSurface(); } protected void onUpdateCameraSurface() { this.mCamera.setSurfaceSize(0, 0, this.mSurfaceWidth, this.mSurfaceHeight); } public int getSurfaceWidth() { return this.mSurfaceWidth; } public int getSurfaceHeight() { return this.mSurfaceHeight; } public ITouchController getTouchController() { return this.mTouchController; } public void setTouchController(final ITouchController pTouchController) { this.mTouchController = pTouchController; this.mTouchController.setTouchEventCallback(this); } public AccelerationData getAccelerationData() { return this.mAccelerationData; } public OrientationData getOrientationData() { return this.mOrientationData; } public VertexBufferObjectManager getVertexBufferObjectManager() { return this.mVertexBufferObjectManager; } public TextureManager getTextureManager() { return this.mTextureManager; } public FontManager getFontManager() { return this.mFontManager; } public ShaderProgramManager getShaderProgramManager() { return this.mShaderProgramManager; } public SoundManager getSoundManager() throws IllegalStateException { if(this.mSoundManager != null) { return this.mSoundManager; } else { throw new IllegalStateException("To enable the SoundManager, check the EngineOptions!"); } } public MusicManager getMusicManager() throws IllegalStateException { if(this.mMusicManager != null) { return this.mMusicManager; } else { throw new IllegalStateException("To enable the MusicManager, check the EngineOptions!"); } } public void registerUpdateHandler(final IUpdateHandler pUpdateHandler) { this.mUpdateHandlers.add(pUpdateHandler); } public void unregisterUpdateHandler(final IUpdateHandler pUpdateHandler) { this.mUpdateHandlers.remove(pUpdateHandler); } public void clearUpdateHandlers() { this.mUpdateHandlers.clear(); } public void registerDrawHandler(final IDrawHandler pDrawHandler) { this.mDrawHandlers.add(pDrawHandler); } public void unregisterDrawHandler(final IDrawHandler pDrawHandler) { this.mDrawHandlers.remove(pDrawHandler); } public void clearDrawHandlers() { this.mDrawHandlers.clear(); } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Override public void onAccuracyChanged(final Sensor pSensor, final int pAccuracy) { if(this.mRunning) { switch(pSensor.getType()) { case Sensor.TYPE_ACCELEROMETER: if(this.mAccelerationData != null) { this.mAccelerationData.setAccuracy(pAccuracy); this.mAccelerationListener.onAccelerationAccuracyChanged(this.mAccelerationData); } else if(this.mOrientationData != null) { this.mOrientationData.setAccelerationAccuracy(pAccuracy); this.mOrientationListener.onOrientationAccuracyChanged(this.mOrientationData); } break; case Sensor.TYPE_MAGNETIC_FIELD: this.mOrientationData.setMagneticFieldAccuracy(pAccuracy); this.mOrientationListener.onOrientationAccuracyChanged(this.mOrientationData); break; } } } @Override public void onSensorChanged(final SensorEvent pEvent) { if(this.mRunning) { switch(pEvent.sensor.getType()) { case Sensor.TYPE_ACCELEROMETER: if(this.mAccelerationData != null) { this.mAccelerationData.setValues(pEvent.values); this.mAccelerationListener.onAccelerationChanged(this.mAccelerationData); } else if(this.mOrientationData != null) { this.mOrientationData.setAccelerationValues(pEvent.values); this.mOrientationListener.onOrientationChanged(this.mOrientationData); } break; case Sensor.TYPE_MAGNETIC_FIELD: this.mOrientationData.setMagneticFieldValues(pEvent.values); this.mOrientationListener.onOrientationChanged(this.mOrientationData); break; } } } @Override public void onLocationChanged(final Location pLocation) { if(this.mLocation == null) { this.mLocation = pLocation; } else { if(pLocation == null) { this.mLocationListener.onLocationLost(); } else { this.mLocation = pLocation; this.mLocationListener.onLocationChanged(pLocation); } } } @Override public void onProviderDisabled(final String pProvider) { this.mLocationListener.onLocationProviderDisabled(); } @Override public void onProviderEnabled(final String pProvider) { this.mLocationListener.onLocationProviderEnabled(); } @Override public void onStatusChanged(final String pProvider, final int pStatus, final Bundle pExtras) { switch(pStatus) { case LocationProvider.AVAILABLE: this.mLocationListener.onLocationProviderStatusChanged(LocationProviderStatus.AVAILABLE, pExtras); break; case LocationProvider.OUT_OF_SERVICE: this.mLocationListener.onLocationProviderStatusChanged(LocationProviderStatus.OUT_OF_SERVICE, pExtras); break; case LocationProvider.TEMPORARILY_UNAVAILABLE: this.mLocationListener.onLocationProviderStatusChanged(LocationProviderStatus.TEMPORARILY_UNAVAILABLE, pExtras); break; } } @Override public boolean onTouch(final View pView, final MotionEvent pSurfaceMotionEvent) { if(this.mRunning) { this.mTouchController.onHandleMotionEvent(pSurfaceMotionEvent); try { /* Because a human cannot interact 1000x per second, we pause the UI-Thread for a little. */ Thread.sleep(this.mEngineOptions.getTouchOptions().getTouchEventIntervalMilliseconds()); } catch (final InterruptedException e) { Debug.e(e); } return true; } else { return false; } } @Override public boolean onTouchEvent(final TouchEvent pSurfaceTouchEvent) { /* * Let the engine determine which scene and camera this event should be * handled by. */ final Scene scene = this.getSceneFromSurfaceTouchEvent(pSurfaceTouchEvent); final Camera camera = this.getCameraFromSurfaceTouchEvent(pSurfaceTouchEvent); this.convertSurfaceToSceneTouchEvent(camera, pSurfaceTouchEvent); if(this.onTouchHUD(camera, pSurfaceTouchEvent)) { return true; } else { /* If HUD didn't handle it, Scene may handle it. */ return this.onTouchScene(scene, pSurfaceTouchEvent); } } protected boolean onTouchHUD(final Camera pCamera, final TouchEvent pSceneTouchEvent) { if(pCamera.hasHUD()) { return pCamera.getHUD().onSceneTouchEvent(pSceneTouchEvent); } else { return false; } } protected boolean onTouchScene(final Scene pScene, final TouchEvent pSceneTouchEvent) { if(pScene != null) { return pScene.onSceneTouchEvent(pSceneTouchEvent); } else { return false; } } // =========================================================== // Methods // =========================================================== public void runOnUpdateThread(final Runnable pRunnable) { this.runOnUpdateThread(pRunnable, true); } /** * This method is useful when you want to execute code on the {@link UpdateThread}, even though the Engine is paused. * * @param pRunnable the {@link Runnable} to be run on the {@link UpdateThread}. * @param pOnlyWhenEngineRunning if <code>true</code>, the execution of the {@link Runnable} will be delayed until the next time {@link Engine#onUpdateUpdateHandlers(float)} is picked up, which is when {@link Engine#isRunning()} is <code>true</code>. * if <code>false</code>, the execution of the {@link Runnable} will happen as soon as possible on the {@link UpdateThread}, no matter what {@link Engine#isRunning()} is. */ public void runOnUpdateThread(final Runnable pRunnable, final boolean pOnlyWhenEngineRunning) { if(pOnlyWhenEngineRunning) { this.mUpdateThreadRunnableHandler.postRunnable(pRunnable); } else { this.mUpdateThread.postRunnable(pRunnable); } } /** * @param pRunnable the {@link Runnable} to run mutually exclusive to the {@link UpdateThread} and the GL-{@link Thread}. * When the caller already is on the {@link UpdateThread} or the GL-{@link Thread}, the {@link Runnable} is executed immediately. * @see {@link Engine#getEngineLock()} to manually {@link EngineLock#lock()}/{@link EngineLock#unlock()} on, while avoiding creating a {@link Runnable}. */ public void runSafely(final Runnable pRunnable) { this.mEngineLock.lock(); try { pRunnable.run(); } finally { this.mEngineLock.unlock(); } } public void onDestroy() { this.mEngineLock.lock(); try { this.mDestroyed = true; this.mEngineLock.notifyCanUpdate(); } finally { this.mEngineLock.unlock(); } try { this.mUpdateThread.join(); } catch (final InterruptedException e) { Debug.e("Could not join UpdateThread.", e); Debug.w("Trying to manually interrupt UpdateThread."); this.mUpdateThread.interrupt(); } this.mVertexBufferObjectManager.onDestroy(); this.mTextureManager.onDestroy(); this.mFontManager.onDestroy(); this.mShaderProgramManager.onDestroy(); } public void onReloadResources() { this.mVertexBufferObjectManager.onReload(); this.mTextureManager.onReload(); this.mFontManager.onReload(); this.mShaderProgramManager.onReload(); } protected Camera getCameraFromSurfaceTouchEvent(final TouchEvent pTouchEvent) { return this.getCamera(); } protected Scene getSceneFromSurfaceTouchEvent(final TouchEvent pTouchEvent) { return this.mScene; } protected void convertSurfaceToSceneTouchEvent(final Camera pCamera, final TouchEvent pSurfaceTouchEvent) { pCamera.convertSurfaceToSceneTouchEvent(pSurfaceTouchEvent, this.mSurfaceWidth, this.mSurfaceHeight); } protected void convertSceneToSurfaceTouchEvent(final Camera pCamera, final TouchEvent pSurfaceTouchEvent) { pCamera.convertSceneToSurfaceTouchEvent(pSurfaceTouchEvent, this.mSurfaceWidth, this.mSurfaceHeight); } void onTickUpdate() throws InterruptedException { if(this.mRunning) { final long secondsElapsed = this.getNanosecondsElapsed(); this.mEngineLock.lock(); try { this.throwOnDestroyed(); this.onUpdate(secondsElapsed); this.throwOnDestroyed(); this.mEngineLock.notifyCanDraw(); this.mEngineLock.waitUntilCanUpdate(); } finally { this.mEngineLock.unlock(); } } else { this.mEngineLock.lock(); try { this.throwOnDestroyed(); this.mEngineLock.notifyCanDraw(); this.mEngineLock.waitUntilCanUpdate(); } finally { this.mEngineLock.unlock(); } Thread.sleep(16); } } private void throwOnDestroyed() throws EngineDestroyedException { if(this.mDestroyed) { throw new EngineDestroyedException(); } } public void onUpdate(final long pNanosecondsElapsed) throws InterruptedException { final float pSecondsElapsed = pNanosecondsElapsed * TimeConstants.SECONDS_PER_NANOSECOND; this.mSecondsElapsedTotal += pSecondsElapsed; this.mLastTick += pNanosecondsElapsed; this.mTouchController.onUpdate(pSecondsElapsed); this.onUpdateUpdateHandlers(pSecondsElapsed); this.onUpdateScene(pSecondsElapsed); } protected void onUpdateScene(final float pSecondsElapsed) { if(this.mScene != null) { this.mScene.onUpdate(pSecondsElapsed); } } protected void onUpdateUpdateHandlers(final float pSecondsElapsed) { this.mUpdateThreadRunnableHandler.onUpdate(pSecondsElapsed); this.mUpdateHandlers.onUpdate(pSecondsElapsed); this.getCamera().onUpdate(pSecondsElapsed); } protected void onUpdateDrawHandlers(final GLState pGLState, final Camera pCamera) { this.mDrawHandlers.onDraw(pGLState, pCamera); } public void onDrawFrame(final GLState pGLState) throws InterruptedException { final EngineLock engineLock = this.mEngineLock; engineLock.lock(); try { engineLock.waitUntilCanDraw(); this.mVertexBufferObjectManager.updateVertexBufferObjects(pGLState); this.mTextureManager.updateTextures(pGLState); this.mFontManager.updateFonts(pGLState); this.onUpdateDrawHandlers(pGLState, this.mCamera); this.onDrawScene(pGLState, this.mCamera); engineLock.notifyCanUpdate(); } finally { engineLock.unlock(); } } protected void onDrawScene(final GLState pGLState, final Camera pCamera) { if(this.mScene != null) { this.mScene.onDraw(pGLState, pCamera); } pCamera.onDrawHUD(pGLState); } private long getNanosecondsElapsed() { final long now = System.nanoTime(); return now - this.mLastTick; } public boolean enableVibrator(final Context pContext) { this.mVibrator = (Vibrator) pContext.getSystemService(Context.VIBRATOR_SERVICE); return this.mVibrator != null; } public void vibrate(final long pMilliseconds) throws IllegalStateException { if(this.mVibrator != null) { this.mVibrator.vibrate(pMilliseconds); } else { throw new IllegalStateException("You need to enable the Vibrator before you can use it!"); } } public void vibrate(final long[] pPattern, final int pRepeat) throws IllegalStateException { if(this.mVibrator != null) { this.mVibrator.vibrate(pPattern, pRepeat); } else { throw new IllegalStateException("You need to enable the Vibrator before you can use it!"); } } public void enableLocationSensor(final Context pContext, final ILocationListener pLocationListener, final LocationSensorOptions pLocationSensorOptions) { this.mLocationListener = pLocationListener; final LocationManager locationManager = (LocationManager) pContext.getSystemService(Context.LOCATION_SERVICE); final String locationProvider = locationManager.getBestProvider(pLocationSensorOptions, pLocationSensorOptions.isEnabledOnly()); // TODO locationProvider can be null, in that case return false. Successful case should return true. locationManager.requestLocationUpdates(locationProvider, pLocationSensorOptions.getMinimumTriggerTime(), pLocationSensorOptions.getMinimumTriggerDistance(), this); this.onLocationChanged(locationManager.getLastKnownLocation(locationProvider)); } public void disableLocationSensor(final Context pContext) { final LocationManager locationManager = (LocationManager) pContext.getSystemService(Context.LOCATION_SERVICE); locationManager.removeUpdates(this); } /** * @see {@link Engine#enableAccelerationSensor(Context, IAccelerationListener, AccelerationSensorOptions)} */ public boolean enableAccelerationSensor(final Context pContext, final IAccelerationListener pAccelerationListener) { return this.enableAccelerationSensor(pContext, pAccelerationListener, new AccelerationSensorOptions(Engine.SENSORDELAY_DEFAULT)); } /** * @return <code>true</code> when the sensor was successfully enabled, <code>false</code> otherwise. */ public boolean enableAccelerationSensor(final Context pContext, final IAccelerationListener pAccelerationListener, final AccelerationSensorOptions pAccelerationSensorOptions) { final SensorManager sensorManager = (SensorManager) pContext.getSystemService(Context.SENSOR_SERVICE); if(Engine.isSensorSupported(sensorManager, Sensor.TYPE_ACCELEROMETER)) { this.mAccelerationListener = pAccelerationListener; if(this.mAccelerationData == null) { final Display display = ((WindowManager) pContext.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); final int displayRotation = display.getOrientation(); this.mAccelerationData = new AccelerationData(displayRotation); } this.registerSelfAsSensorListener(sensorManager, Sensor.TYPE_ACCELEROMETER, pAccelerationSensorOptions.getSensorDelay()); return true; } else { return false; } } /** * @return <code>true</code> when the sensor was successfully disabled, <code>false</code> otherwise. */ public boolean disableAccelerationSensor(final Context pContext) { final SensorManager sensorManager = (SensorManager) pContext.getSystemService(Context.SENSOR_SERVICE); if(Engine.isSensorSupported(sensorManager, Sensor.TYPE_ACCELEROMETER)) { this.unregisterSelfAsSensorListener(sensorManager, Sensor.TYPE_ACCELEROMETER); return true; } else { return false; } } /** * @see {@link Engine#enableOrientationSensor(Context, IOrientationListener, OrientationSensorOptions)} */ public boolean enableOrientationSensor(final Context pContext, final IOrientationListener pOrientationListener) { return this.enableOrientationSensor(pContext, pOrientationListener, new OrientationSensorOptions(Engine.SENSORDELAY_DEFAULT)); } /** * @return <code>true</code> when the sensor was successfully enabled, <code>false</code> otherwise. */ public boolean enableOrientationSensor(final Context pContext, final IOrientationListener pOrientationListener, final OrientationSensorOptions pOrientationSensorOptions) { final SensorManager sensorManager = (SensorManager) pContext.getSystemService(Context.SENSOR_SERVICE); if(Engine.isSensorSupported(sensorManager, Sensor.TYPE_ACCELEROMETER) && Engine.isSensorSupported(sensorManager, Sensor.TYPE_MAGNETIC_FIELD)) { this.mOrientationListener = pOrientationListener; if(this.mOrientationData == null) { final Display display = ((WindowManager) pContext.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); final int displayRotation = display.getOrientation(); this.mOrientationData = new OrientationData(displayRotation); } this.registerSelfAsSensorListener(sensorManager, Sensor.TYPE_ACCELEROMETER, pOrientationSensorOptions.getSensorDelay()); this.registerSelfAsSensorListener(sensorManager, Sensor.TYPE_MAGNETIC_FIELD, pOrientationSensorOptions.getSensorDelay()); return true; } else { return false; } } /** * @return <code>true</code> when the sensor was successfully disabled, <code>false</code> otherwise. */ public boolean disableOrientationSensor(final Context pContext) { final SensorManager sensorManager = (SensorManager) pContext.getSystemService(Context.SENSOR_SERVICE); if(Engine.isSensorSupported(sensorManager, Sensor.TYPE_ACCELEROMETER) && Engine.isSensorSupported(sensorManager, Sensor.TYPE_MAGNETIC_FIELD)) { this.unregisterSelfAsSensorListener(sensorManager, Sensor.TYPE_ACCELEROMETER); this.unregisterSelfAsSensorListener(sensorManager, Sensor.TYPE_MAGNETIC_FIELD); return true; } else { return false; } } private static boolean isSensorSupported(final SensorManager pSensorManager, final int pType) { return pSensorManager.getSensorList(pType).size() > 0; } private void registerSelfAsSensorListener(final SensorManager pSensorManager, final int pType, final SensorDelay pSensorDelay) { final Sensor sensor = pSensorManager.getSensorList(pType).get(0); pSensorManager.registerListener(this, sensor, pSensorDelay.getDelay()); } private void unregisterSelfAsSensorListener(final SensorManager pSensorManager, final int pType) { final Sensor sensor = pSensorManager.getSensorList(pType).get(0); pSensorManager.unregisterListener(this, sensor); } // =========================================================== // Inner and Anonymous Classes // =========================================================== public static class UpdateThread extends Thread { // =========================================================== // Constants // =========================================================== // =========================================================== // Fields // =========================================================== private Engine mEngine; private final RunnableHandler mRunnableHandler = new RunnableHandler(); // =========================================================== // Constructors // =========================================================== public UpdateThread() { super(UpdateThread.class.getSimpleName()); } // =========================================================== // Getter & Setter // =========================================================== public void setEngine(final Engine pEngine) { this.mEngine = pEngine; } // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== @Override public void run() { android.os.Process.setThreadPriority(this.mEngine.getEngineOptions().getUpdateThreadPriority()); try { while(true) { this.mRunnableHandler.onUpdate(0); this.mEngine.onTickUpdate(); } } catch (final InterruptedException e) { if(BuildConfig.DEBUG) { Debug.d(this.getClass().getSimpleName() + " interrupted. Don't worry - this " + e.getClass().getSimpleName() + " is most likely expected!", e); } this.interrupt(); } } // =========================================================== // Methods // =========================================================== public void postRunnable(final Runnable pRunnable) { this.mRunnableHandler.postRunnable(pRunnable); } // =========================================================== // Inner and Anonymous Classes // =========================================================== } public class EngineDestroyedException extends InterruptedException { // =========================================================== // Constants // =========================================================== private static final long serialVersionUID = -4691263961728972560L; // =========================================================== // Fields // =========================================================== // =========================================================== // Constructors // =========================================================== // =========================================================== // Getter & Setter // =========================================================== // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== // =========================================================== // Methods // =========================================================== // =========================================================== // Inner and Anonymous Classes // =========================================================== } public static class EngineLock extends ReentrantLock { // =========================================================== // Constants // =========================================================== private static final long serialVersionUID = 671220941302523934L; // =========================================================== // Fields // =========================================================== final Condition mDrawingCondition = this.newCondition(); final AtomicBoolean mDrawing = new AtomicBoolean(false); // =========================================================== // Constructors // =========================================================== public EngineLock(final boolean pFair) { super(pFair); } // =========================================================== // Getter & Setter // =========================================================== // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== // =========================================================== // Methods // =========================================================== void notifyCanDraw() { this.mDrawing.set(true); this.mDrawingCondition.signalAll(); } void notifyCanUpdate() { this.mDrawing.set(false); this.mDrawingCondition.signalAll(); } void waitUntilCanDraw() throws InterruptedException { while(!this.mDrawing.get()) { this.mDrawingCondition.await(); } } void waitUntilCanUpdate() throws InterruptedException { while(this.mDrawing.get()) { this.mDrawingCondition.await(); } } // =========================================================== // Inner and Anonymous Classes // =========================================================== } }