SDLSurface.java 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. package org.libsdl.app;
  2. import android.content.Context;
  3. import android.content.pm.ActivityInfo;
  4. import android.hardware.Sensor;
  5. import android.hardware.SensorEvent;
  6. import android.hardware.SensorEventListener;
  7. import android.hardware.SensorManager;
  8. import android.os.Build;
  9. import android.util.DisplayMetrics;
  10. import android.util.Log;
  11. import android.view.Display;
  12. import android.view.InputDevice;
  13. import android.view.KeyEvent;
  14. import android.view.MotionEvent;
  15. import android.view.Surface;
  16. import android.view.SurfaceHolder;
  17. import android.view.SurfaceView;
  18. import android.view.View;
  19. import android.view.WindowManager;
  20. /**
  21. SDLSurface. This is what we draw on, so we need to know when it's created
  22. in order to do anything useful.
  23. Because of this, that's where we set up the SDL thread
  24. */
  25. public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
  26. View.OnKeyListener, View.OnTouchListener, SensorEventListener {
  27. // Sensors
  28. protected SensorManager mSensorManager;
  29. protected Display mDisplay;
  30. // Keep track of the surface size to normalize touch events
  31. protected float mWidth, mHeight;
  32. // Is SurfaceView ready for rendering
  33. public boolean mIsSurfaceReady;
  34. // Startup
  35. public SDLSurface(Context context) {
  36. super(context);
  37. getHolder().addCallback(this);
  38. setFocusable(true);
  39. setFocusableInTouchMode(true);
  40. requestFocus();
  41. setOnKeyListener(this);
  42. setOnTouchListener(this);
  43. mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
  44. mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
  45. setOnGenericMotionListener(SDLActivity.getMotionListener());
  46. // Some arbitrary defaults to avoid a potential division by zero
  47. mWidth = 1.0f;
  48. mHeight = 1.0f;
  49. mIsSurfaceReady = false;
  50. }
  51. public void handlePause() {
  52. enableSensor(Sensor.TYPE_ACCELEROMETER, false);
  53. }
  54. public void handleResume() {
  55. setFocusable(true);
  56. setFocusableInTouchMode(true);
  57. requestFocus();
  58. setOnKeyListener(this);
  59. setOnTouchListener(this);
  60. enableSensor(Sensor.TYPE_ACCELEROMETER, true);
  61. }
  62. public Surface getNativeSurface() {
  63. return getHolder().getSurface();
  64. }
  65. // Called when we have a valid drawing surface
  66. @Override
  67. public void surfaceCreated(SurfaceHolder holder) {
  68. Log.v("SDL", "surfaceCreated()");
  69. SDLActivity.onNativeSurfaceCreated();
  70. }
  71. // Called when we lose the surface
  72. @Override
  73. public void surfaceDestroyed(SurfaceHolder holder) {
  74. Log.v("SDL", "surfaceDestroyed()");
  75. // Transition to pause, if needed
  76. SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED;
  77. SDLActivity.handleNativeState();
  78. mIsSurfaceReady = false;
  79. SDLActivity.onNativeSurfaceDestroyed();
  80. }
  81. // Called when the surface is resized
  82. @Override
  83. public void surfaceChanged(SurfaceHolder holder,
  84. int format, int width, int height) {
  85. Log.v("SDL", "surfaceChanged()");
  86. if (SDLActivity.mSingleton == null) {
  87. return;
  88. }
  89. mWidth = width;
  90. mHeight = height;
  91. int nDeviceWidth = width;
  92. int nDeviceHeight = height;
  93. try
  94. {
  95. if (Build.VERSION.SDK_INT >= 17) {
  96. DisplayMetrics realMetrics = new DisplayMetrics();
  97. mDisplay.getRealMetrics( realMetrics );
  98. nDeviceWidth = realMetrics.widthPixels;
  99. nDeviceHeight = realMetrics.heightPixels;
  100. }
  101. } catch(Exception ignored) {
  102. }
  103. synchronized(SDLActivity.getContext()) {
  104. // In case we're waiting on a size change after going fullscreen, send a notification.
  105. SDLActivity.getContext().notifyAll();
  106. }
  107. Log.v("SDL", "Window size: " + width + "x" + height);
  108. Log.v("SDL", "Device size: " + nDeviceWidth + "x" + nDeviceHeight);
  109. SDLActivity.nativeSetScreenResolution(width, height, nDeviceWidth, nDeviceHeight, mDisplay.getRefreshRate());
  110. SDLActivity.onNativeResize();
  111. // Prevent a screen distortion glitch,
  112. // for instance when the device is in Landscape and a Portrait App is resumed.
  113. boolean skip = false;
  114. int requestedOrientation = SDLActivity.mSingleton.getRequestedOrientation();
  115. if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) {
  116. if (mWidth > mHeight) {
  117. skip = true;
  118. }
  119. } else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) {
  120. if (mWidth < mHeight) {
  121. skip = true;
  122. }
  123. }
  124. // Special Patch for Square Resolution: Black Berry Passport
  125. if (skip) {
  126. double min = Math.min(mWidth, mHeight);
  127. double max = Math.max(mWidth, mHeight);
  128. if (max / min < 1.20) {
  129. Log.v("SDL", "Don't skip on such aspect-ratio. Could be a square resolution.");
  130. skip = false;
  131. }
  132. }
  133. // Don't skip in MultiWindow.
  134. if (skip) {
  135. if (Build.VERSION.SDK_INT >= 24) {
  136. if (SDLActivity.mSingleton.isInMultiWindowMode()) {
  137. Log.v("SDL", "Don't skip in Multi-Window");
  138. skip = false;
  139. }
  140. }
  141. }
  142. if (skip) {
  143. Log.v("SDL", "Skip .. Surface is not ready.");
  144. mIsSurfaceReady = false;
  145. return;
  146. }
  147. /* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */
  148. SDLActivity.onNativeSurfaceChanged();
  149. /* Surface is ready */
  150. mIsSurfaceReady = true;
  151. SDLActivity.mNextNativeState = SDLActivity.NativeState.RESUMED;
  152. SDLActivity.handleNativeState();
  153. }
  154. // Key events
  155. @Override
  156. public boolean onKey(View v, int keyCode, KeyEvent event) {
  157. return SDLActivity.handleKeyEvent(v, keyCode, event, null);
  158. }
  159. // Touch events
  160. @Override
  161. public boolean onTouch(View v, MotionEvent event) {
  162. /* Ref: http://developer.android.com/training/gestures/multi.html */
  163. int touchDevId = event.getDeviceId();
  164. final int pointerCount = event.getPointerCount();
  165. int action = event.getActionMasked();
  166. int pointerFingerId;
  167. int i = -1;
  168. float x,y,p;
  169. /*
  170. * Prevent id to be -1, since it's used in SDL internal for synthetic events
  171. * Appears when using Android emulator, eg:
  172. * adb shell input mouse tap 100 100
  173. * adb shell input touchscreen tap 100 100
  174. */
  175. if (touchDevId < 0) {
  176. touchDevId -= 1;
  177. }
  178. // 12290 = Samsung DeX mode desktop mouse
  179. // 12290 = 0x3002 = 0x2002 | 0x1002 = SOURCE_MOUSE | SOURCE_TOUCHSCREEN
  180. // 0x2 = SOURCE_CLASS_POINTER
  181. if (event.getSource() == InputDevice.SOURCE_MOUSE || event.getSource() == (InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN)) {
  182. int mouseButton = 1;
  183. try {
  184. Object object = event.getClass().getMethod("getButtonState").invoke(event);
  185. if (object != null) {
  186. mouseButton = (Integer) object;
  187. }
  188. } catch(Exception ignored) {
  189. }
  190. // We need to check if we're in relative mouse mode and get the axis offset rather than the x/y values
  191. // if we are. We'll leverage our existing mouse motion listener
  192. SDLGenericMotionListener_API12 motionListener = SDLActivity.getMotionListener();
  193. x = motionListener.getEventX(event);
  194. y = motionListener.getEventY(event);
  195. SDLActivity.onNativeMouse(mouseButton, action, x, y, motionListener.inRelativeMode());
  196. } else {
  197. switch(action) {
  198. case MotionEvent.ACTION_MOVE:
  199. for (i = 0; i < pointerCount; i++) {
  200. pointerFingerId = event.getPointerId(i);
  201. x = event.getX(i) / mWidth;
  202. y = event.getY(i) / mHeight;
  203. p = event.getPressure(i);
  204. if (p > 1.0f) {
  205. // may be larger than 1.0f on some devices
  206. // see the documentation of getPressure(i)
  207. p = 1.0f;
  208. }
  209. SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
  210. }
  211. break;
  212. case MotionEvent.ACTION_UP:
  213. case MotionEvent.ACTION_DOWN:
  214. // Primary pointer up/down, the index is always zero
  215. i = 0;
  216. /* fallthrough */
  217. case MotionEvent.ACTION_POINTER_UP:
  218. case MotionEvent.ACTION_POINTER_DOWN:
  219. // Non primary pointer up/down
  220. if (i == -1) {
  221. i = event.getActionIndex();
  222. }
  223. pointerFingerId = event.getPointerId(i);
  224. x = event.getX(i) / mWidth;
  225. y = event.getY(i) / mHeight;
  226. p = event.getPressure(i);
  227. if (p > 1.0f) {
  228. // may be larger than 1.0f on some devices
  229. // see the documentation of getPressure(i)
  230. p = 1.0f;
  231. }
  232. SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
  233. break;
  234. case MotionEvent.ACTION_CANCEL:
  235. for (i = 0; i < pointerCount; i++) {
  236. pointerFingerId = event.getPointerId(i);
  237. x = event.getX(i) / mWidth;
  238. y = event.getY(i) / mHeight;
  239. p = event.getPressure(i);
  240. if (p > 1.0f) {
  241. // may be larger than 1.0f on some devices
  242. // see the documentation of getPressure(i)
  243. p = 1.0f;
  244. }
  245. SDLActivity.onNativeTouch(touchDevId, pointerFingerId, MotionEvent.ACTION_UP, x, y, p);
  246. }
  247. break;
  248. default:
  249. break;
  250. }
  251. }
  252. return true;
  253. }
  254. // Sensor events
  255. public void enableSensor(int sensortype, boolean enabled) {
  256. // TODO: This uses getDefaultSensor - what if we have >1 accels?
  257. if (enabled) {
  258. mSensorManager.registerListener(this,
  259. mSensorManager.getDefaultSensor(sensortype),
  260. SensorManager.SENSOR_DELAY_GAME, null);
  261. } else {
  262. mSensorManager.unregisterListener(this,
  263. mSensorManager.getDefaultSensor(sensortype));
  264. }
  265. }
  266. @Override
  267. public void onAccuracyChanged(Sensor sensor, int accuracy) {
  268. // TODO
  269. }
  270. @Override
  271. public void onSensorChanged(SensorEvent event) {
  272. if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
  273. // Since we may have an orientation set, we won't receive onConfigurationChanged events.
  274. // We thus should check here.
  275. int newOrientation;
  276. float x, y;
  277. switch (mDisplay.getRotation()) {
  278. case Surface.ROTATION_90:
  279. x = -event.values[1];
  280. y = event.values[0];
  281. newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE;
  282. break;
  283. case Surface.ROTATION_270:
  284. x = event.values[1];
  285. y = -event.values[0];
  286. newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE_FLIPPED;
  287. break;
  288. case Surface.ROTATION_180:
  289. x = -event.values[0];
  290. y = -event.values[1];
  291. newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT_FLIPPED;
  292. break;
  293. case Surface.ROTATION_0:
  294. default:
  295. x = event.values[0];
  296. y = event.values[1];
  297. newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT;
  298. break;
  299. }
  300. if (newOrientation != SDLActivity.mCurrentOrientation) {
  301. SDLActivity.mCurrentOrientation = newOrientation;
  302. SDLActivity.onNativeOrientationChanged(newOrientation);
  303. }
  304. SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH,
  305. y / SensorManager.GRAVITY_EARTH,
  306. event.values[2] / SensorManager.GRAVITY_EARTH);
  307. }
  308. }
  309. // Captured pointer events for API 26.
  310. public boolean onCapturedPointerEvent(MotionEvent event)
  311. {
  312. int action = event.getActionMasked();
  313. float x, y;
  314. switch (action) {
  315. case MotionEvent.ACTION_SCROLL:
  316. x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
  317. y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
  318. SDLActivity.onNativeMouse(0, action, x, y, false);
  319. return true;
  320. case MotionEvent.ACTION_HOVER_MOVE:
  321. case MotionEvent.ACTION_MOVE:
  322. x = event.getX(0);
  323. y = event.getY(0);
  324. SDLActivity.onNativeMouse(0, action, x, y, true);
  325. return true;
  326. case MotionEvent.ACTION_BUTTON_PRESS:
  327. case MotionEvent.ACTION_BUTTON_RELEASE:
  328. // Change our action value to what SDL's code expects.
  329. if (action == MotionEvent.ACTION_BUTTON_PRESS) {
  330. action = MotionEvent.ACTION_DOWN;
  331. } else { /* MotionEvent.ACTION_BUTTON_RELEASE */
  332. action = MotionEvent.ACTION_UP;
  333. }
  334. x = event.getX(0);
  335. y = event.getY(0);
  336. int button = event.getButtonState();
  337. SDLActivity.onNativeMouse(button, action, x, y, true);
  338. return true;
  339. }
  340. return false;
  341. }
  342. }