/* * Copyright (C) 2016 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.apps.santatracker.games.simpleengine; import android.app.Activity; import android.content.Context; import android.content.SharedPreferences; import android.graphics.PointF; import android.hardware.SensorEvent; import android.os.Vibrator; import android.util.SparseArray; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.Surface; import com.google.android.apps.santatracker.games.jetpack.JetpackConfig; import java.lang.ref.WeakReference; import java.util.ArrayList; public class SceneManager { private static final String TAG = "SceneManager"; private static SceneManager instance = new SceneManager(); private Renderer mRenderer = new Renderer(); private SoundManager mSoundManager = null; private Scene mCurScene = null; private Scene mNewScene = null; private long mLastFrameTime = -1; private boolean mHasGL = false; private Vibrator mVibrator; private Context mAppContext = null; // reference to Activity, if it's in the resumed state -- otherwise null private WeakReference<Activity> mActivity = new WeakReference<Activity>(null); private boolean mActivityResumed = false; private boolean mActivityHasFocus = false; // queue of MotionEvents to process from the game thread private ArrayList<OurMotionEvent> mMotionEventQueue = new ArrayList<OurMotionEvent>(32); private ArrayList<OurMotionEvent> mMotionEventRecycle = new ArrayList<OurMotionEvent>(32); private ArrayList<OurSensorEvent> mSensorEventQueue = new ArrayList<>(32); private ArrayList<OurSensorEvent> mSensorEventRecycle = new ArrayList<>(32); private ArrayList<OurMotionEvent> mTmpMotionEvent = new ArrayList<OurMotionEvent>(32); private ArrayList<OurSensorEvent> mTmpSensorEvent = new ArrayList<>(32); // this flag is raised by the UI thread when it adds something to mMotionEventQueue // and lowered by the game thread when it processes the motion event queue. This flag // should only be modified when mMotionEventQueue is locked; it can be read without // locking. private volatile boolean mCheckMotionEvents = false; private boolean mLargePresentMode = false; private volatile boolean mCheckSensorEvents = false; // last x, y of pointer, keyed by pointer ID private SparseArray<PointF> mLastTouchCoords = new SparseArray<PointF>(); // recycle bin of PointF objects private ArrayList<PointF> mPointRecycleBin = new ArrayList<PointF>(); private SceneManager() { } public static SceneManager getInstance() { return instance; } void onGLSurfaceCreated(Context ctx) { mHasGL = true; mAppContext = ctx.getApplicationContext(); mRenderer.onGLSurfaceCreated(mAppContext); if (mSoundManager == null) { mSoundManager = new SoundManager(ctx); } } void onGLSurfaceChanged(int width, int height) { mRenderer.onGLSurfaceChanged(width, height); if (mCurScene != null) { mCurScene.onScreenResized(width, height); } } private void installNewScene() { if (mCurScene != null) { mCurScene.onUninstall(); mRenderer.reset(); mSoundManager.reset(); } mCurScene = mNewScene; mNewScene = null; if (mCurScene != null) { mCurScene.onInstall(); mRenderer.startLoadingTexs(mAppContext); } } public void onPause() { mActivityResumed = false; if (mSoundManager != null) { mSoundManager.stopSound(); } mActivity.clear(); } public void onResume(Activity activity) { mActivityResumed = true; mActivity = new WeakReference<Activity>(activity); if (mSoundManager != null && mActivityHasFocus) { mSoundManager.resumeSound(); } } public void setLargePresentMode(boolean largePresentMode) { mLargePresentMode = largePresentMode; } public boolean getLargePresentMode() { return mLargePresentMode; } public void loadMute() { if(getSoundManager() != null && getActivity() != null) { SharedPreferences sharedPreferences = getActivity().getSharedPreferences( JetpackConfig.Keys.JETPACK_PREFERENCES, Activity.MODE_PRIVATE); mSoundManager.setMute(sharedPreferences.getBoolean( JetpackConfig.Keys.JETPACK_MUTE_KEY, false)); } } public void saveMute() { if(mSoundManager != null) { SharedPreferences sharedPreferences = getActivity().getSharedPreferences( JetpackConfig.Keys.JETPACK_PREFERENCES, Activity.MODE_PRIVATE); sharedPreferences.edit().putBoolean( JetpackConfig.Keys.JETPACK_MUTE_KEY, mSoundManager.getMute()) .apply(); } } public Activity getActivity() { return mActivity.get(); } public void onFocusChanged(boolean focus) { mActivityHasFocus = focus; if (!focus) { mSoundManager.stopSound(); } else if (mActivityResumed && mSoundManager != null) { mSoundManager.resumeSound(); } } public Vibrator getVibrator() { if(mVibrator == null) { mVibrator = ((Vibrator)mAppContext.getSystemService(Context.VIBRATOR_SERVICE)); } return mVibrator; } public boolean shouldBePlaying() { return mActivityResumed && mActivityHasFocus; } public Scene getCurrentScene() { return mCurScene; } void onDrawFrame() { if (!mHasGL) { Logger.w("Ignoring request to do frame without a GL surface."); return; } if (mNewScene != null) { installNewScene(); } if (mCurScene != null) { if (mLastFrameTime < 0) { mLastFrameTime = System.currentTimeMillis(); } float deltaT = (System.currentTimeMillis() - mLastFrameTime) * 0.001f; mLastFrameTime = System.currentTimeMillis(); if (mRenderer.prepareFrame() && mSoundManager.isReady()) { mCurScene.doFrame(deltaT); } else { mCurScene.doStandbyFrame(deltaT); } } mRenderer.doFrame(); // process touch events if (mCheckMotionEvents) { processMotionEvents(); } if (mCheckSensorEvents) { processSensorEvents(); } } public void enableDebugLog(boolean enable) { Logger.enableDebugLog(enable); } public void requestNewScene(Scene c) { mNewScene = c; } public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { return false; } else { processKeyEvent(keyCode, event); return true; } } public boolean onKeyUp(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { return false; } else { processKeyEvent(keyCode, event); return true; } } public void onSensorChanged(SensorEvent event) { float x=0, y=0; int rotation = Surface.ROTATION_90; if (getActivity() != null) { // Store the current screen rotation (used to offset the readings of the sensor). rotation = getActivity().getWindowManager().getDefaultDisplay().getRotation(); } // Handle screen rotations by interpreting the sensor readings here // Game is locked in 90 degree rotation so that config is assumed. x = event.values[1]; y = -event.values[0]; queueSensorEvent(x, y, event.accuracy); } public boolean onTouchEvent(MotionEvent event) { // we are running on the UI thread, so deliver the event to the queue, // where the game thread will pick it up to process synchronized (mMotionEventQueue) { int action = event.getActionMasked(); // get updates about each pointer in the gesture int i; for (i = 0; i < event.getPointerCount(); i++) { int pointerId = event.getPointerId(i); float x = event.getX(i); float y = event.getY(i); // figure out delta from last touch event float deltaX = x - getLastTouchX(pointerId, x); float deltaY = y - getLastTouchY(pointerId, y); // queue the motion event queueMotionEvent(MotionEvent.ACTION_MOVE, pointerId, x, y, deltaX, deltaY); // update last touch coordinates setLastTouchCoords(pointerId, x, y); } // figure out if a pointer went up or down int id; PointF point; switch (action) { case MotionEvent.ACTION_UP: case MotionEvent.ACTION_POINTER_UP: id = event.getPointerId(event.getActionIndex()); forgetLastTouchCoords(id); queueMotionEvent(MotionEvent.ACTION_UP, id, event.getX(), event.getY(), 0.0f, 0.0f); break; case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_POINTER_DOWN: id = event.getPointerId(event.getActionIndex()); setLastTouchCoords(id, event.getX(), event.getY()); queueMotionEvent(MotionEvent.ACTION_DOWN, id, event.getX(), event.getY(), 0.0f, 0.0f); break; } } return true; } private float getLastTouchX(int pointerId, float defaultX) { PointF pt = mLastTouchCoords.get(pointerId, null); return pt != null ? pt.x : defaultX; } private float getLastTouchY(int pointerId, float defaultY) { PointF pt = mLastTouchCoords.get(pointerId, null); return pt != null ? pt.y : defaultY; } private void setLastTouchCoords(int pointerId, float x, float y) { PointF pt = mLastTouchCoords.get(pointerId, null); if (pt == null) { pt = allocPointF(); } pt.x = x; pt.y = y; mLastTouchCoords.put(pointerId, pt); } private void forgetLastTouchCoords(int pointerId) { PointF pt = mLastTouchCoords.get(pointerId, null); if (pt != null) { mLastTouchCoords.remove(pointerId); recyclePointF(pt); } } private PointF allocPointF() { if (mPointRecycleBin.size() > 0) { PointF p = mPointRecycleBin.remove(mPointRecycleBin.size() - 1); p.x = p.y = 0.0f; return p; } return new PointF(); } private void recyclePointF(PointF p) { mPointRecycleBin.add(p); } private void queueMotionEvent(int action, int pointerId, float screenX, float screenY, float deltaX, float deltaY) { OurMotionEvent e = mMotionEventRecycle.size() > 0 ? mMotionEventRecycle.remove(mMotionEventRecycle.size() - 1) : new OurMotionEvent(); e.action = action; e.pointerId = pointerId; e.screenX = screenX; e.screenY = screenY; e.deltaX = deltaX; e.deltaY = deltaY; mMotionEventQueue.add(e); mCheckMotionEvents = true; } private void queueSensorEvent(float x, float y, int accuracy) { OurSensorEvent e = mSensorEventRecycle.size() > 0 ? mSensorEventRecycle.remove(mSensorEventRecycle.size() - 1) : new OurSensorEvent(); e.x = x; e.y = y; e.accuracy = accuracy; mSensorEventQueue.add(e); mCheckSensorEvents = true; } public Renderer getRenderer() { return mRenderer; } public SoundManager getSoundManager() { return mSoundManager; } private void processSensorEvents() { int i; synchronized(mSensorEventQueue) { for (i = 0; i < mSensorEventQueue.size(); i++) { OurSensorEvent e = mSensorEventQueue.get(i); if(e != null) { mTmpSensorEvent.add(mSensorEventQueue.get(i)); } } mSensorEventQueue.clear(); mCheckSensorEvents = false; } // process the sensor events for (i = 0; i < mTmpSensorEvent.size(); i++) { processSensorEvent(mTmpSensorEvent.get(i)); } // recycle the objects synchronized (mSensorEventQueue) { for (i = 0; i < mTmpMotionEvent.size(); i++) { mSensorEventRecycle.add(mTmpSensorEvent.get(i)); } mTmpSensorEvent.clear(); } } private void processSensorEvent(OurSensorEvent e) { if (mCurScene == null) { return; } if(e != null) { mCurScene.onSensorChanged(e.x, e.y, e.accuracy); } } private void processMotionEvents() { int i; // move array items to our temporary array so we can unlock the original synchronized (mMotionEventQueue) { for (i = 0; i < mMotionEventQueue.size(); i++) { mTmpMotionEvent.add(mMotionEventQueue.get(i)); } mMotionEventQueue.clear(); mCheckMotionEvents = false; } // process the motion events for (i = 0; i < mTmpMotionEvent.size(); i++) { processMotionEvent(mTmpMotionEvent.get(i)); } // recycle the objects synchronized (mMotionEventQueue) { for (i = 0; i < mTmpMotionEvent.size(); i++) { mMotionEventRecycle.add(mTmpMotionEvent.get(i)); } mTmpMotionEvent.clear(); } } private void processMotionEvent(OurMotionEvent event) { if (mCurScene == null) { return; } // convert the screen coordinates to our standard coordinate system float x = mRenderer.convertScreenX(event.screenX); float y = mRenderer.convertScreenY(event.screenY); float deltaX = mRenderer.convertScreenDeltaX(event.deltaX); float deltaY = mRenderer.convertScreenDeltaY(event.deltaY); switch (event.action) { case MotionEvent.ACTION_DOWN: mCurScene.onPointerDown(event.pointerId, x, y); break; case MotionEvent.ACTION_MOVE: mCurScene.onPointerMove(event.pointerId, x, y, deltaX, deltaY); break; case MotionEvent.ACTION_UP: mCurScene.onPointerUp(event.pointerId, x, y); break; } } private void processKeyEvent(int keyCode, KeyEvent event) { if (mCurScene == null) { return; } // convert the screen coordinates to our standard coordinate system switch (event.getAction()) { case KeyEvent.ACTION_DOWN: mCurScene.onKeyDown(keyCode, event.getRepeatCount()); break; case KeyEvent.ACTION_UP: mCurScene.onKeyUp(keyCode); break; } } private class OurMotionEvent { int action; int pointerId; float screenX, screenY; float deltaX, deltaY; } private class OurSensorEvent { float x; float y; int accuracy; } }