diff --git a/src/openrct2-android/app/src/main/java/org/libsdl/app/SDLActivity.java b/src/openrct2-android/app/src/main/java/org/libsdl/app/SDLActivity.java index b51698f27d..040af7a38d 100644 --- a/src/openrct2-android/app/src/main/java/org/libsdl/app/SDLActivity.java +++ b/src/openrct2-android/app/src/main/java/org/libsdl/app/SDLActivity.java @@ -53,6 +53,7 @@ import android.widget.Toast; import java.util.Hashtable; import java.util.Locale; +import org.libsdl.app.SDLSurface; /** SDL Activity diff --git a/src/openrct2-android/app/src/main/java/org/libsdl/app/SDLSurface.java b/src/openrct2-android/app/src/main/java/org/libsdl/app/SDLSurface.java new file mode 100644 index 0000000000..74ecaf5bf4 --- /dev/null +++ b/src/openrct2-android/app/src/main/java/org/libsdl/app/SDLSurface.java @@ -0,0 +1,413 @@ +package org.libsdl.app; + + +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.os.Build; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.Display; +import android.view.InputDevice; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.WindowManager; + + +/** + SDLSurface. This is what we draw on, so we need to know when it's created + in order to do anything useful. + + Because of this, that's where we set up the SDL thread +*/ +public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, + View.OnKeyListener, View.OnTouchListener, SensorEventListener { + + // Sensors + protected SensorManager mSensorManager; + protected Display mDisplay; + + // Keep track of the surface size to normalize touch events + protected float mWidth, mHeight; + + // Is SurfaceView ready for rendering + public boolean mIsSurfaceReady; + + // Startup + public SDLSurface(Context context) { + super(context); + getHolder().addCallback(this); + + setFocusable(true); + setFocusableInTouchMode(true); + requestFocus(); + setOnKeyListener(this); + setOnTouchListener(this); + + mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); + mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); + + setOnGenericMotionListener(SDLActivity.getMotionListener()); + + // Some arbitrary defaults to avoid a potential division by zero + mWidth = 1.0f; + mHeight = 1.0f; + + mIsSurfaceReady = false; + } + + public void handlePause() { + enableSensor(Sensor.TYPE_ACCELEROMETER, false); + } + + public void handleResume() { + setFocusable(true); + setFocusableInTouchMode(true); + requestFocus(); + setOnKeyListener(this); + setOnTouchListener(this); + enableSensor(Sensor.TYPE_ACCELEROMETER, true); + } + + public Surface getNativeSurface() { + return getHolder().getSurface(); + } + + // Called when we have a valid drawing surface + @Override + public void surfaceCreated(SurfaceHolder holder) { + Log.v("SDL", "surfaceCreated()"); + SDLActivity.onNativeSurfaceCreated(); + } + + // Called when we lose the surface + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + Log.v("SDL", "surfaceDestroyed()"); + + // Transition to pause, if needed + SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED; + SDLActivity.handleNativeState(); + + mIsSurfaceReady = false; + SDLActivity.onNativeSurfaceDestroyed(); + } + + // Called when the surface is resized + @Override + public void surfaceChanged(SurfaceHolder holder, + int format, int width, int height) { + Log.v("SDL", "surfaceChanged()"); + + if (SDLActivity.mSingleton == null) { + return; + } + + mWidth = width; + mHeight = height; + int nDeviceWidth = width; + int nDeviceHeight = height; + float density = 1.0f; + try + { + if (Build.VERSION.SDK_INT >= 17 /* Android 4.2 (JELLY_BEAN_MR1) */) { + DisplayMetrics realMetrics = new DisplayMetrics(); + mDisplay.getRealMetrics( realMetrics ); + nDeviceWidth = realMetrics.widthPixels; + nDeviceHeight = realMetrics.heightPixels; + // Use densityDpi instead of density to more closely match what the UI scale is + density = (float)realMetrics.densityDpi / 160.0f; + } + } catch(Exception ignored) { + } + + synchronized(SDLActivity.getContext()) { + // In case we're waiting on a size change after going fullscreen, send a notification. + SDLActivity.getContext().notifyAll(); + } + + Log.v("SDL", "Window size: " + width + "x" + height); + Log.v("SDL", "Device size: " + nDeviceWidth + "x" + nDeviceHeight); + SDLActivity.nativeSetScreenResolution(width, height, nDeviceWidth, nDeviceHeight, density, mDisplay.getRefreshRate()); + SDLActivity.onNativeResize(); + + // Prevent a screen distortion glitch, + // for instance when the device is in Landscape and a Portrait App is resumed. + boolean skip = false; + int requestedOrientation = SDLActivity.mSingleton.getRequestedOrientation(); + + if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) { + if (mWidth > mHeight) { + skip = true; + } + } else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) { + if (mWidth < mHeight) { + skip = true; + } + } + + // Special Patch for Square Resolution: Black Berry Passport + if (skip) { + double min = Math.min(mWidth, mHeight); + double max = Math.max(mWidth, mHeight); + + if (max / min < 1.20) { + Log.v("SDL", "Don't skip on such aspect-ratio. Could be a square resolution."); + skip = false; + } + } + + // Don't skip if we might be multi-window or have popup dialogs + if (skip) { + if (Build.VERSION.SDK_INT >= 24 /* Android 7.0 (N) */) { + skip = false; + } + } + + if (skip) { + Log.v("SDL", "Skip .. Surface is not ready."); + mIsSurfaceReady = false; + return; + } + + /* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */ + SDLActivity.onNativeSurfaceChanged(); + + /* Surface is ready */ + mIsSurfaceReady = true; + + SDLActivity.mNextNativeState = SDLActivity.NativeState.RESUMED; + SDLActivity.handleNativeState(); + } + + // Key events + @Override + public boolean onKey(View v, int keyCode, KeyEvent event) { + return SDLActivity.handleKeyEvent(v, keyCode, event, null); + } + + private float getNormalizedX(float x) + { + if (mWidth <= 1) { + return 0.5f; + } else { + return (x / (mWidth - 1)); + } + } + + private float getNormalizedY(float y) + { + if (mHeight <= 1) { + return 0.5f; + } else { + return (y / (mHeight - 1)); + } + } + + // Touch events + @Override + public boolean onTouch(View v, MotionEvent event) { + /* Ref: http://developer.android.com/training/gestures/multi.html */ + int touchDevId = event.getDeviceId(); + final int pointerCount = event.getPointerCount(); + int action = event.getActionMasked(); + int pointerFingerId; + int i = -1; + float x,y,p; + + // 12290 = Samsung DeX mode desktop mouse + // 12290 = 0x3002 = 0x2002 | 0x1002 = SOURCE_MOUSE | SOURCE_TOUCHSCREEN + // 0x2 = SOURCE_CLASS_POINTER + if (event.getSource() == InputDevice.SOURCE_MOUSE || event.getSource() == (InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN)) { + int mouseButton = 1; + try { + Object object = event.getClass().getMethod("getButtonState").invoke(event); + if (object != null) { + mouseButton = (Integer) object; + } + } catch(Exception ignored) { + } + + // We need to check if we're in relative mouse mode and get the axis offset rather than the x/y values + // if we are. We'll leverage our existing mouse motion listener + SDLGenericMotionListener_API12 motionListener = SDLActivity.getMotionListener(); + x = motionListener.getEventX(event); + y = motionListener.getEventY(event); + + SDLActivity.onNativeMouse(mouseButton, action, x, y, motionListener.inRelativeMode()); + } else { + switch(action) { + case MotionEvent.ACTION_MOVE: + for (i = 0; i < pointerCount; i++) { + pointerFingerId = event.getPointerId(i); + x = getNormalizedX(event.getX(i)); + y = getNormalizedY(event.getY(i)); + p = event.getPressure(i); + if (p > 1.0f) { + // may be larger than 1.0f on some devices + // see the documentation of getPressure(i) + p = 1.0f; + } + SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p); + } + break; + + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_DOWN: + // Primary pointer up/down, the index is always zero + i = 0; + /* fallthrough */ + case MotionEvent.ACTION_POINTER_UP: + case MotionEvent.ACTION_POINTER_DOWN: + // Non primary pointer up/down + if (i == -1) { + i = event.getActionIndex(); + } + + pointerFingerId = event.getPointerId(i); + x = getNormalizedX(event.getX(i)); + y = getNormalizedY(event.getY(i)); + p = event.getPressure(i); + if (p > 1.0f) { + // may be larger than 1.0f on some devices + // see the documentation of getPressure(i) + p = 1.0f; + } + SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p); + break; + + case MotionEvent.ACTION_CANCEL: + for (i = 0; i < pointerCount; i++) { + pointerFingerId = event.getPointerId(i); + x = getNormalizedX(event.getX(i)); + y = getNormalizedY(event.getY(i)); + p = event.getPressure(i); + if (p > 1.0f) { + // may be larger than 1.0f on some devices + // see the documentation of getPressure(i) + p = 1.0f; + } + SDLActivity.onNativeTouch(touchDevId, pointerFingerId, MotionEvent.ACTION_UP, x, y, p); + } + break; + + default: + break; + } + } + + return true; + } + + // Sensor events + public void enableSensor(int sensortype, boolean enabled) { + // TODO: This uses getDefaultSensor - what if we have >1 accels? + if (enabled) { + mSensorManager.registerListener(this, + mSensorManager.getDefaultSensor(sensortype), + SensorManager.SENSOR_DELAY_GAME, null); + } else { + mSensorManager.unregisterListener(this, + mSensorManager.getDefaultSensor(sensortype)); + } + } + + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + // TODO + } + + @Override + public void onSensorChanged(SensorEvent event) { + if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { + + // Since we may have an orientation set, we won't receive onConfigurationChanged events. + // We thus should check here. + int newRotation; + + float x, y; + switch (mDisplay.getRotation()) { + case Surface.ROTATION_0: + default: + x = event.values[0]; + y = event.values[1]; + newRotation = 0; + break; + case Surface.ROTATION_90: + x = -event.values[1]; + y = event.values[0]; + newRotation = 90; + break; + case Surface.ROTATION_180: + x = -event.values[0]; + y = -event.values[1]; + newRotation = 180; + break; + case Surface.ROTATION_270: + x = event.values[1]; + y = -event.values[0]; + newRotation = 270; + break; + } + + if (newRotation != SDLActivity.mCurrentRotation) { + SDLActivity.mCurrentRotation = newRotation; + SDLActivity.onNativeRotationChanged(newRotation); + } + + SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH, + y / SensorManager.GRAVITY_EARTH, + event.values[2] / SensorManager.GRAVITY_EARTH); + + + } + } + + // Captured pointer events for API 26. + public boolean onCapturedPointerEvent(MotionEvent event) + { + int action = event.getActionMasked(); + + float x, y; + switch (action) { + case MotionEvent.ACTION_SCROLL: + x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0); + y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0); + SDLActivity.onNativeMouse(0, action, x, y, false); + return true; + + case MotionEvent.ACTION_HOVER_MOVE: + case MotionEvent.ACTION_MOVE: + x = event.getX(0); + y = event.getY(0); + SDLActivity.onNativeMouse(0, action, x, y, true); + return true; + + case MotionEvent.ACTION_BUTTON_PRESS: + case MotionEvent.ACTION_BUTTON_RELEASE: + + // Change our action value to what SDL's code expects. + if (action == MotionEvent.ACTION_BUTTON_PRESS) { + action = MotionEvent.ACTION_DOWN; + } else { /* MotionEvent.ACTION_BUTTON_RELEASE */ + action = MotionEvent.ACTION_UP; + } + + x = event.getX(0); + y = event.getY(0); + int button = event.getButtonState(); + + SDLActivity.onNativeMouse(button, action, x, y, true); + return true; + } + + return false; + } +}