EbitenView.java 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. // Copyright 2022 The Ebitengine Authors
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package {{.JavaPkg}}.{{.PrefixLower}};
  15. import java.util.ArrayList;
  16. import java.util.Collections;
  17. import java.util.Comparator;
  18. import java.util.List;
  19. import android.content.Context;
  20. import android.hardware.input.InputManager;
  21. import android.os.Handler;
  22. import android.os.Looper;
  23. import android.util.AttributeSet;
  24. import android.util.DisplayMetrics;
  25. import android.util.Log;
  26. import android.view.Display;
  27. import android.view.KeyEvent;
  28. import android.view.InputDevice;
  29. import android.view.MotionEvent;
  30. import android.view.ViewGroup;
  31. import android.view.WindowManager;
  32. import {{.JavaPkg}}.ebitenmobileview.Ebitenmobileview;
  33. public class EbitenView extends ViewGroup implements InputManager.InputDeviceListener {
  34. static class Gamepad {
  35. public int deviceId;
  36. public ArrayList<InputDevice.MotionRange> axes;
  37. public ArrayList<InputDevice.MotionRange> hats;
  38. }
  39. // See https://github.com/libsdl-org/SDL/blob/2df2da11f627299c6e05b7e0aff407c915043372/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java#L154-L173
  40. static class RangeComparator implements Comparator<InputDevice.MotionRange> {
  41. @Override
  42. public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) {
  43. int arg0Axis = arg0.getAxis();
  44. int arg1Axis = arg1.getAxis();
  45. if (arg0Axis == MotionEvent.AXIS_GAS) {
  46. arg0Axis = MotionEvent.AXIS_BRAKE;
  47. } else if (arg0Axis == MotionEvent.AXIS_BRAKE) {
  48. arg0Axis = MotionEvent.AXIS_GAS;
  49. }
  50. if (arg1Axis == MotionEvent.AXIS_GAS) {
  51. arg1Axis = MotionEvent.AXIS_BRAKE;
  52. } else if (arg1Axis == MotionEvent.AXIS_BRAKE) {
  53. arg1Axis = MotionEvent.AXIS_GAS;
  54. }
  55. // Make sure the AXIS_Z is sorted between AXIS_RY and AXIS_RZ.
  56. //
  57. // The value ordering on Android otherwise is AXIS_X, AXIS_Y,
  58. // all kinds of axes used by touchscreens or touchpads only,
  59. // AXIS_Z, AXIS_RX, AXIS_RY, AXIS_RZ, hats, triggers,
  60. // flight controls, car controls, misc stuff.
  61. //
  62. // This is because the usual pairing are:
  63. // - AXIS_X, AXIS_Y (left stick).
  64. // - AXIS_RX, AXIS_RY (sometimes the right stick, sometimes triggers).
  65. // - AXIS_Z, AXIS_RZ (sometimes the right stick, sometimes triggers).
  66. //
  67. // This sorts the axes in the above order, which tends to be correct
  68. // for Xbox-ish game pads that have the right stick on RX/RY and the
  69. // triggers on Z/RZ.
  70. //
  71. // Gamepads that don't have AXIS_Z/AXIS_RZ but use
  72. // AXIS_LTRIGGER/AXIS_RTRIGGER are unaffected by this.
  73. //
  74. // References:
  75. // - https://developer.android.com/develop/ui/views/touch-and-input/game-controllers/controller-input
  76. // - https://www.kernel.org/doc/html/latest/input/gamepad.html
  77. if (arg0Axis == MotionEvent.AXIS_Z) {
  78. arg0Axis = MotionEvent.AXIS_RZ - 1;
  79. } else if (arg0Axis > MotionEvent.AXIS_Z && arg0Axis < MotionEvent.AXIS_RZ) {
  80. arg0Axis--;
  81. }
  82. if (arg1Axis == MotionEvent.AXIS_Z) {
  83. arg1Axis = MotionEvent.AXIS_RZ - 1;
  84. } else if (arg1Axis > MotionEvent.AXIS_Z && arg1Axis < MotionEvent.AXIS_RZ) {
  85. arg1Axis--;
  86. }
  87. return arg0Axis - arg1Axis;
  88. }
  89. }
  90. private static double pxToDp(double x) {
  91. return x / Ebitenmobileview.deviceScale();
  92. }
  93. public EbitenView(Context context) {
  94. super(context);
  95. initialize(context);
  96. }
  97. public EbitenView(Context context, AttributeSet attrs) {
  98. super(context, attrs);
  99. initialize(context);
  100. }
  101. private void initialize(Context context) {
  102. this.gamepads = new ArrayList<Gamepad>();
  103. this.ebitenSurfaceView = new EbitenSurfaceView(getContext());
  104. LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
  105. addView(this.ebitenSurfaceView, params);
  106. this.inputManager = (InputManager)context.getSystemService(Context.INPUT_SERVICE);
  107. this.inputManager.registerInputDeviceListener(this, null);
  108. for (int id : this.inputManager.getInputDeviceIds()) {
  109. this.onInputDeviceAdded(id);
  110. }
  111. }
  112. @Override
  113. protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
  114. this.ebitenSurfaceView.layout(0, 0, right - left, bottom - top);
  115. double widthInDp = pxToDp(right - left);
  116. double heightInDp = pxToDp(bottom - top);
  117. Ebitenmobileview.layout(widthInDp, heightInDp);
  118. }
  119. @Override
  120. public boolean onKeyDown(int keyCode, KeyEvent event) {
  121. Ebitenmobileview.onKeyDownOnAndroid(keyCode, event.getUnicodeChar(), event.getSource(), event.getDeviceId());
  122. return true;
  123. }
  124. @Override
  125. public boolean onKeyUp(int keyCode, KeyEvent event) {
  126. Ebitenmobileview.onKeyUpOnAndroid(keyCode, event.getSource(), event.getDeviceId());
  127. return true;
  128. }
  129. @Override
  130. public boolean onTouchEvent(MotionEvent e) {
  131. // getActionIndex returns a valid value only for the action whose index is the returned value of getActionIndex (#2220).
  132. // See https://developer.android.com/reference/android/view/MotionEvent#getActionMasked().
  133. // For other pointers, treat their actions as MotionEvent.ACTION_MOVE.
  134. int touchIndex = e.getActionIndex();
  135. for (int i = 0; i < e.getPointerCount(); i++) {
  136. int id = e.getPointerId(i);
  137. int x = (int)e.getX(i);
  138. int y = (int)e.getY(i);
  139. int action = (i == touchIndex) ? e.getActionMasked() : MotionEvent.ACTION_MOVE;
  140. Ebitenmobileview.updateTouchesOnAndroid(action, id, (int)pxToDp(x), (int)pxToDp(y));
  141. }
  142. return true;
  143. }
  144. private Gamepad getGamepad(int deviceId) {
  145. for (Gamepad gamepad : this.gamepads) {
  146. if (gamepad.deviceId == deviceId) {
  147. return gamepad;
  148. }
  149. }
  150. return null;
  151. }
  152. @Override
  153. public boolean onGenericMotionEvent(MotionEvent event) {
  154. if ((event.getSource() & InputDevice.SOURCE_JOYSTICK) != InputDevice.SOURCE_JOYSTICK) {
  155. return super.onGenericMotionEvent(event);
  156. }
  157. if (event.getAction() != MotionEvent.ACTION_MOVE) {
  158. return super.onGenericMotionEvent(event);
  159. }
  160. // See https://github.com/libsdl-org/SDL/blob/2df2da11f627299c6e05b7e0aff407c915043372/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java#L256-L277
  161. Gamepad gamepad = this.getGamepad(event.getDeviceId());
  162. if (gamepad == null) {
  163. return true;
  164. }
  165. int actionPointerIndex = event.getActionIndex();
  166. for (int i = 0; i < gamepad.axes.size(); i++) {
  167. InputDevice.MotionRange range = gamepad.axes.get(i);
  168. float axisValue = event.getAxisValue(range.getAxis(), actionPointerIndex);
  169. float value = (axisValue - range.getMin()) / range.getRange() * 2.0f - 1.0f;
  170. Ebitenmobileview.onGamepadAxisChanged(gamepad.deviceId, i, value);
  171. }
  172. for (int i = 0; i < gamepad.hats.size() / 2; i++) {
  173. int hatX = Math.round(event.getAxisValue(gamepad.hats.get(2*i).getAxis(), actionPointerIndex));
  174. int hatY = Math.round(event.getAxisValue(gamepad.hats.get(2*i+1).getAxis(), actionPointerIndex));
  175. Ebitenmobileview.onGamepadHatChanged(gamepad.deviceId, i, hatX, hatY);
  176. }
  177. return true;
  178. }
  179. @Override
  180. public void onInputDeviceAdded(int deviceId) {
  181. InputDevice inputDevice = this.inputManager.getInputDevice(deviceId);
  182. // The InputDevice can be null on some deivces (#1342).
  183. if (inputDevice == null) {
  184. return;
  185. }
  186. // A fingerprint reader is unexpectedly recognized as a joystick. Skip this (#1542).
  187. if (inputDevice.getName().equals("uinput-fpc")) {
  188. return;
  189. }
  190. int sources = inputDevice.getSources();
  191. if ((sources & InputDevice.SOURCE_GAMEPAD) != InputDevice.SOURCE_GAMEPAD &&
  192. (sources & InputDevice.SOURCE_JOYSTICK) != InputDevice.SOURCE_JOYSTICK) {
  193. return;
  194. }
  195. // See https://github.com/libsdl-org/SDL/blob/2df2da11f627299c6e05b7e0aff407c915043372/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java#L182-L216
  196. List<InputDevice.MotionRange> ranges = inputDevice.getMotionRanges();
  197. Collections.sort(ranges, new RangeComparator());
  198. Gamepad gamepad = new Gamepad();
  199. gamepad.deviceId = deviceId;
  200. gamepad.axes = new ArrayList<InputDevice.MotionRange>();
  201. gamepad.hats = new ArrayList<InputDevice.MotionRange>();
  202. for (InputDevice.MotionRange range : ranges) {
  203. if (range.getAxis() == MotionEvent.AXIS_HAT_X || range.getAxis() == MotionEvent.AXIS_HAT_Y) {
  204. gamepad.hats.add(range);
  205. } else {
  206. gamepad.axes.add(range);
  207. }
  208. }
  209. this.gamepads.add(gamepad);
  210. String descriptor = inputDevice.getDescriptor();
  211. int vendorId = inputDevice.getVendorId();
  212. int productId = inputDevice.getProductId();
  213. // These values are required to calculate SDL's GUID.
  214. int buttonMask = getButtonMask(inputDevice, gamepad.hats.size()/2);
  215. int axisMask = getAxisMask(inputDevice);
  216. Ebitenmobileview.onGamepadAdded(deviceId, inputDevice.getName(), gamepad.axes.size(), gamepad.hats.size()/2, descriptor, vendorId, productId, buttonMask, axisMask);
  217. }
  218. // The implementation is copied from SDL:
  219. // https://github.com/libsdl-org/SDL/blob/0e9560aea22818884921e5e5064953257bfe7fa7/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java#L308
  220. private static int getButtonMask(InputDevice joystickDevice, int nhats) {
  221. int buttonMask = 0;
  222. int[] keys = new int[] {
  223. KeyEvent.KEYCODE_BUTTON_A,
  224. KeyEvent.KEYCODE_BUTTON_B,
  225. KeyEvent.KEYCODE_BUTTON_X,
  226. KeyEvent.KEYCODE_BUTTON_Y,
  227. KeyEvent.KEYCODE_BACK,
  228. KeyEvent.KEYCODE_BUTTON_MODE,
  229. KeyEvent.KEYCODE_BUTTON_START,
  230. KeyEvent.KEYCODE_BUTTON_THUMBL,
  231. KeyEvent.KEYCODE_BUTTON_THUMBR,
  232. KeyEvent.KEYCODE_BUTTON_L1,
  233. KeyEvent.KEYCODE_BUTTON_R1,
  234. KeyEvent.KEYCODE_DPAD_UP,
  235. KeyEvent.KEYCODE_DPAD_DOWN,
  236. KeyEvent.KEYCODE_DPAD_LEFT,
  237. KeyEvent.KEYCODE_DPAD_RIGHT,
  238. KeyEvent.KEYCODE_BUTTON_SELECT,
  239. KeyEvent.KEYCODE_DPAD_CENTER,
  240. // These don't map into any SDL controller buttons directly
  241. KeyEvent.KEYCODE_BUTTON_L2,
  242. KeyEvent.KEYCODE_BUTTON_R2,
  243. KeyEvent.KEYCODE_BUTTON_C,
  244. KeyEvent.KEYCODE_BUTTON_Z,
  245. KeyEvent.KEYCODE_BUTTON_1,
  246. KeyEvent.KEYCODE_BUTTON_2,
  247. KeyEvent.KEYCODE_BUTTON_3,
  248. KeyEvent.KEYCODE_BUTTON_4,
  249. KeyEvent.KEYCODE_BUTTON_5,
  250. KeyEvent.KEYCODE_BUTTON_6,
  251. KeyEvent.KEYCODE_BUTTON_7,
  252. KeyEvent.KEYCODE_BUTTON_8,
  253. KeyEvent.KEYCODE_BUTTON_9,
  254. KeyEvent.KEYCODE_BUTTON_10,
  255. KeyEvent.KEYCODE_BUTTON_11,
  256. KeyEvent.KEYCODE_BUTTON_12,
  257. KeyEvent.KEYCODE_BUTTON_13,
  258. KeyEvent.KEYCODE_BUTTON_14,
  259. KeyEvent.KEYCODE_BUTTON_15,
  260. KeyEvent.KEYCODE_BUTTON_16,
  261. };
  262. int[] masks = new int[] {
  263. (1 << 0), // A -> A
  264. (1 << 1), // B -> B
  265. (1 << 2), // X -> X
  266. (1 << 3), // Y -> Y
  267. (1 << 4), // BACK -> BACK
  268. (1 << 5), // MODE -> GUIDE
  269. (1 << 6), // START -> START
  270. (1 << 7), // THUMBL -> LEFTSTICK
  271. (1 << 8), // THUMBR -> RIGHTSTICK
  272. (1 << 9), // L1 -> LEFTSHOULDER
  273. (1 << 10), // R1 -> RIGHTSHOULDER
  274. (1 << 11), // DPAD_UP -> DPAD_UP
  275. (1 << 12), // DPAD_DOWN -> DPAD_DOWN
  276. (1 << 13), // DPAD_LEFT -> DPAD_LEFT
  277. (1 << 14), // DPAD_RIGHT -> DPAD_RIGHT
  278. (1 << 4), // SELECT -> BACK
  279. (1 << 0), // DPAD_CENTER -> A
  280. (1 << 15), // L2 -> ??
  281. (1 << 16), // R2 -> ??
  282. (1 << 17), // C -> ??
  283. (1 << 18), // Z -> ??
  284. (1 << 20), // 1 -> ??
  285. (1 << 21), // 2 -> ??
  286. (1 << 22), // 3 -> ??
  287. (1 << 23), // 4 -> ??
  288. (1 << 24), // 5 -> ??
  289. (1 << 25), // 6 -> ??
  290. (1 << 26), // 7 -> ??
  291. (1 << 27), // 8 -> ??
  292. (1 << 28), // 9 -> ??
  293. (1 << 29), // 10 -> ??
  294. (1 << 30), // 11 -> ??
  295. (1 << 31), // 12 -> ??
  296. // We're out of room...
  297. 0xFFFFFFFF, // 13 -> ??
  298. 0xFFFFFFFF, // 14 -> ??
  299. 0xFFFFFFFF, // 15 -> ??
  300. 0xFFFFFFFF, // 16 -> ??
  301. };
  302. boolean[] hasKeys = joystickDevice.hasKeys(keys);
  303. for (int i = 0; i < keys.length; ++i) {
  304. if (hasKeys[i]) {
  305. buttonMask |= masks[i];
  306. }
  307. }
  308. // https://github.com/libsdl-org/SDL/blob/47f2373dc13b66c48bf4024fcdab53cd0bdd59bb/src/joystick/android/SDL_sysjoystick.c#L360-L367
  309. if (nhats > 0) {
  310. // Add Dpad buttons.
  311. buttonMask |= 1 << 11;
  312. buttonMask |= 1 << 12;
  313. buttonMask |= 1 << 13;
  314. buttonMask |= 1 << 14;
  315. }
  316. return buttonMask;
  317. }
  318. private static int getAxisMask(InputDevice joystickDevice) {
  319. final int SDL_CONTROLLER_AXIS_LEFTX = 0;
  320. final int SDL_CONTROLLER_AXIS_LEFTY = 1;
  321. final int SDL_CONTROLLER_AXIS_RIGHTX = 2;
  322. final int SDL_CONTROLLER_AXIS_RIGHTY = 3;
  323. final int SDL_CONTROLLER_AXIS_TRIGGERLEFT = 4;
  324. final int SDL_CONTROLLER_AXIS_TRIGGERRIGHT = 5;
  325. int naxes = 0;
  326. boolean haveZ = false;
  327. boolean havePastZBeforeRZ = false;
  328. for (InputDevice.MotionRange range : joystickDevice.getMotionRanges()) {
  329. if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
  330. int axis = range.getAxis();
  331. if (axis != MotionEvent.AXIS_HAT_X && axis != MotionEvent.AXIS_HAT_Y) {
  332. naxes++;
  333. }
  334. if (axis == MotionEvent.AXIS_Z) {
  335. haveZ = true;
  336. } else if (axis > MotionEvent.AXIS_Z && axis < MotionEvent.AXIS_RZ) {
  337. havePastZBeforeRZ = true;
  338. }
  339. }
  340. }
  341. // The variable is_accelerometer seems always false, then skip the checking:
  342. // https://github.com/libsdl-org/SDL/blob/0e9560aea22818884921e5e5064953257bfe7fa7/android-project/app/src/main/java/org/libsdl/app/SDLControllerManager.java#L207
  343. int axisMask = 0;
  344. if (naxes >= 2) {
  345. axisMask |= ((1 << SDL_CONTROLLER_AXIS_LEFTX) | (1 << SDL_CONTROLLER_AXIS_LEFTY));
  346. }
  347. if (naxes >= 4) {
  348. axisMask |= ((1 << SDL_CONTROLLER_AXIS_RIGHTX) | (1 << SDL_CONTROLLER_AXIS_RIGHTY));
  349. }
  350. if (naxes >= 6) {
  351. axisMask |= ((1 << SDL_CONTROLLER_AXIS_TRIGGERLEFT) | (1 << SDL_CONTROLLER_AXIS_TRIGGERRIGHT));
  352. }
  353. // Also add an indicator bit for whether the sorting order has changed.
  354. // This serves to disable outdated gamecontrollerdb.txt mappings.
  355. if (haveZ && havePastZBeforeRZ) {
  356. axisMask |= 0x8000;
  357. }
  358. return axisMask;
  359. }
  360. @Override
  361. public void onInputDeviceChanged(int deviceId) {
  362. // Do nothing.
  363. }
  364. @Override
  365. public void onInputDeviceRemoved(int deviceId) {
  366. // Do not call inputManager.getInputDevice(), which returns null (#1185).
  367. Ebitenmobileview.onInputDeviceRemoved(deviceId);
  368. this.gamepads.remove(this.getGamepad(deviceId));
  369. }
  370. // suspendGame suspends the game.
  371. // It is recommended to call this when the application is being suspended e.g.,
  372. // Activity's onPause is called.
  373. public void suspendGame() {
  374. this.inputManager.unregisterInputDeviceListener(this);
  375. this.ebitenSurfaceView.onPause();
  376. try {
  377. Ebitenmobileview.suspend();
  378. } catch (final Exception e) {
  379. onErrorOnGameUpdate(e);
  380. }
  381. }
  382. // resumeGame resumes the game.
  383. // It is recommended to call this when the application is being resumed e.g.,
  384. // Activity's onResume is called.
  385. public void resumeGame() {
  386. this.inputManager.registerInputDeviceListener(this, null);
  387. this.ebitenSurfaceView.onResume();
  388. try {
  389. Ebitenmobileview.resume();
  390. } catch (final Exception e) {
  391. onErrorOnGameUpdate(e);
  392. }
  393. }
  394. // onErrorOnGameUpdate is called on the main thread when an error happens when updating a game.
  395. // You can define your own error handler, e.g., using Crashlytics, by overriding this method.
  396. protected void onErrorOnGameUpdate(Exception e) {
  397. Log.e("Go", e.toString());
  398. }
  399. private EbitenSurfaceView ebitenSurfaceView;
  400. private InputManager inputManager;
  401. private ArrayList<Gamepad> gamepads;
  402. }