comparison android/src/org/libsdl/app/SDLActivity.java @ 856:09f5a349e881

Added android project layout
author Michael Pavone <pavone@retrodev.com>
date Wed, 04 Nov 2015 22:11:29 -0800
parents
children 0e5f9d6135be
comparison
equal deleted inserted replaced
855:cb5738176f48 856:09f5a349e881
1 package org.libsdl.app;
2
3 import java.util.ArrayList;
4 import java.util.Arrays;
5 import java.util.Collections;
6 import java.util.Comparator;
7 import java.util.List;
8
9 import android.app.*;
10 import android.content.*;
11 import android.view.*;
12 import android.view.inputmethod.BaseInputConnection;
13 import android.view.inputmethod.EditorInfo;
14 import android.view.inputmethod.InputConnection;
15 import android.view.inputmethod.InputMethodManager;
16 import android.widget.AbsoluteLayout;
17 import android.os.*;
18 import android.util.Log;
19 import android.graphics.*;
20 import android.media.*;
21 import android.hardware.*;
22
23
24 /**
25 SDL Activity
26 */
27 public class SDLActivity extends Activity {
28 private static final String TAG = "SDL";
29
30 // Keep track of the paused state
31 public static boolean mIsPaused, mIsSurfaceReady, mHasFocus;
32 public static boolean mExitCalledFromJava;
33
34 // Main components
35 protected static SDLActivity mSingleton;
36 protected static SDLSurface mSurface;
37 protected static View mTextEdit;
38 protected static ViewGroup mLayout;
39 protected static SDLJoystickHandler mJoystickHandler;
40
41 // This is what SDL runs in. It invokes SDL_main(), eventually
42 protected static Thread mSDLThread;
43
44 // Audio
45 protected static AudioTrack mAudioTrack;
46
47 // Load the .so
48 static {
49 System.loadLibrary("SDL2");
50 //System.loadLibrary("SDL2_image");
51 //System.loadLibrary("SDL2_mixer");
52 //System.loadLibrary("SDL2_net");
53 //System.loadLibrary("SDL2_ttf");
54 System.loadLibrary("main");
55 }
56
57
58 public static void initialize() {
59 // The static nature of the singleton and Android quirkyness force us to initialize everything here
60 // Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values
61 mSingleton = null;
62 mSurface = null;
63 mTextEdit = null;
64 mLayout = null;
65 mJoystickHandler = null;
66 mSDLThread = null;
67 mAudioTrack = null;
68 mExitCalledFromJava = false;
69 mIsPaused = false;
70 mIsSurfaceReady = false;
71 mHasFocus = true;
72 }
73
74 // Setup
75 @Override
76 protected void onCreate(Bundle savedInstanceState) {
77 Log.v("SDL", "onCreate():" + mSingleton);
78 super.onCreate(savedInstanceState);
79
80 SDLActivity.initialize();
81 // So we can call stuff from static callbacks
82 mSingleton = this;
83
84 // Set up the surface
85 mSurface = new SDLSurface(getApplication());
86
87 if(Build.VERSION.SDK_INT >= 12) {
88 mJoystickHandler = new SDLJoystickHandler_API12();
89 }
90 else {
91 mJoystickHandler = new SDLJoystickHandler();
92 }
93
94 mLayout = new AbsoluteLayout(this);
95 mLayout.addView(mSurface);
96
97 setContentView(mLayout);
98 }
99
100 // Events
101 @Override
102 protected void onPause() {
103 Log.v("SDL", "onPause()");
104 super.onPause();
105 SDLActivity.handlePause();
106 }
107
108 @Override
109 protected void onResume() {
110 Log.v("SDL", "onResume()");
111 super.onResume();
112 SDLActivity.handleResume();
113 }
114
115
116 @Override
117 public void onWindowFocusChanged(boolean hasFocus) {
118 super.onWindowFocusChanged(hasFocus);
119 Log.v("SDL", "onWindowFocusChanged(): " + hasFocus);
120
121 SDLActivity.mHasFocus = hasFocus;
122 if (hasFocus) {
123 SDLActivity.handleResume();
124 }
125 }
126
127 @Override
128 public void onLowMemory() {
129 Log.v("SDL", "onLowMemory()");
130 super.onLowMemory();
131 SDLActivity.nativeLowMemory();
132 }
133
134 @Override
135 protected void onDestroy() {
136 Log.v("SDL", "onDestroy()");
137 // Send a quit message to the application
138 SDLActivity.mExitCalledFromJava = true;
139 SDLActivity.nativeQuit();
140
141 // Now wait for the SDL thread to quit
142 if (SDLActivity.mSDLThread != null) {
143 try {
144 SDLActivity.mSDLThread.join();
145 } catch(Exception e) {
146 Log.v("SDL", "Problem stopping thread: " + e);
147 }
148 SDLActivity.mSDLThread = null;
149
150 //Log.v("SDL", "Finished waiting for SDL thread");
151 }
152
153 super.onDestroy();
154 // Reset everything in case the user re opens the app
155 SDLActivity.initialize();
156 }
157
158 @Override
159 public boolean dispatchKeyEvent(KeyEvent event) {
160 int keyCode = event.getKeyCode();
161 // Ignore certain special keys so they're handled by Android
162 if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ||
163 keyCode == KeyEvent.KEYCODE_VOLUME_UP ||
164 keyCode == KeyEvent.KEYCODE_CAMERA ||
165 keyCode == 168 || /* API 11: KeyEvent.KEYCODE_ZOOM_IN */
166 keyCode == 169 /* API 11: KeyEvent.KEYCODE_ZOOM_OUT */
167 ) {
168 return false;
169 }
170 return super.dispatchKeyEvent(event);
171 }
172
173 /** Called by onPause or surfaceDestroyed. Even if surfaceDestroyed
174 * is the first to be called, mIsSurfaceReady should still be set
175 * to 'true' during the call to onPause (in a usual scenario).
176 */
177 public static void handlePause() {
178 if (!SDLActivity.mIsPaused && SDLActivity.mIsSurfaceReady) {
179 SDLActivity.mIsPaused = true;
180 SDLActivity.nativePause();
181 mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, false);
182 }
183 }
184
185 /** Called by onResume or surfaceCreated. An actual resume should be done only when the surface is ready.
186 * Note: Some Android variants may send multiple surfaceChanged events, so we don't need to resume
187 * every time we get one of those events, only if it comes after surfaceDestroyed
188 */
189 public static void handleResume() {
190 if (SDLActivity.mIsPaused && SDLActivity.mIsSurfaceReady && SDLActivity.mHasFocus) {
191 SDLActivity.mIsPaused = false;
192 SDLActivity.nativeResume();
193 mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, true);
194 }
195 }
196
197 /* The native thread has finished */
198 public static void handleNativeExit() {
199 SDLActivity.mSDLThread = null;
200 mSingleton.finish();
201 }
202
203
204 // Messages from the SDLMain thread
205 static final int COMMAND_CHANGE_TITLE = 1;
206 static final int COMMAND_UNUSED = 2;
207 static final int COMMAND_TEXTEDIT_HIDE = 3;
208
209 protected static final int COMMAND_USER = 0x8000;
210
211 /**
212 * This method is called by SDL if SDL did not handle a message itself.
213 * This happens if a received message contains an unsupported command.
214 * Method can be overwritten to handle Messages in a different class.
215 * @param command the command of the message.
216 * @param param the parameter of the message. May be null.
217 * @return if the message was handled in overridden method.
218 */
219 protected boolean onUnhandledMessage(int command, Object param) {
220 return false;
221 }
222
223 /**
224 * A Handler class for Messages from native SDL applications.
225 * It uses current Activities as target (e.g. for the title).
226 * static to prevent implicit references to enclosing object.
227 */
228 protected static class SDLCommandHandler extends Handler {
229 @Override
230 public void handleMessage(Message msg) {
231 Context context = getContext();
232 if (context == null) {
233 Log.e(TAG, "error handling message, getContext() returned null");
234 return;
235 }
236 switch (msg.arg1) {
237 case COMMAND_CHANGE_TITLE:
238 if (context instanceof Activity) {
239 ((Activity) context).setTitle((String)msg.obj);
240 } else {
241 Log.e(TAG, "error handling message, getContext() returned no Activity");
242 }
243 break;
244 case COMMAND_TEXTEDIT_HIDE:
245 if (mTextEdit != null) {
246 mTextEdit.setVisibility(View.GONE);
247
248 InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
249 imm.hideSoftInputFromWindow(mTextEdit.getWindowToken(), 0);
250 }
251 break;
252
253 default:
254 if ((context instanceof SDLActivity) && !((SDLActivity) context).onUnhandledMessage(msg.arg1, msg.obj)) {
255 Log.e(TAG, "error handling message, command is " + msg.arg1);
256 }
257 }
258 }
259 }
260
261 // Handler for the messages
262 Handler commandHandler = new SDLCommandHandler();
263
264 // Send a message from the SDLMain thread
265 boolean sendCommand(int command, Object data) {
266 Message msg = commandHandler.obtainMessage();
267 msg.arg1 = command;
268 msg.obj = data;
269 return commandHandler.sendMessage(msg);
270 }
271
272 // C functions we call
273 public static native void nativeInit();
274 public static native void nativeLowMemory();
275 public static native void nativeQuit();
276 public static native void nativePause();
277 public static native void nativeResume();
278 public static native void onNativeResize(int x, int y, int format);
279 public static native int onNativePadDown(int device_id, int keycode);
280 public static native int onNativePadUp(int device_id, int keycode);
281 public static native void onNativeJoy(int device_id, int axis,
282 float value);
283 public static native void onNativeHat(int device_id, int hat_id,
284 int x, int y);
285 public static native void onNativeKeyDown(int keycode);
286 public static native void onNativeKeyUp(int keycode);
287 public static native void onNativeKeyboardFocusLost();
288 public static native void onNativeTouch(int touchDevId, int pointerFingerId,
289 int action, float x,
290 float y, float p);
291 public static native void onNativeAccel(float x, float y, float z);
292 public static native void onNativeSurfaceChanged();
293 public static native void onNativeSurfaceDestroyed();
294 public static native void nativeFlipBuffers();
295 public static native int nativeAddJoystick(int device_id, String name,
296 int is_accelerometer, int nbuttons,
297 int naxes, int nhats, int nballs);
298 public static native int nativeRemoveJoystick(int device_id);
299
300 public static void flipBuffers() {
301 SDLActivity.nativeFlipBuffers();
302 }
303
304 public static boolean setActivityTitle(String title) {
305 // Called from SDLMain() thread and can't directly affect the view
306 return mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title);
307 }
308
309 public static boolean sendMessage(int command, int param) {
310 return mSingleton.sendCommand(command, Integer.valueOf(param));
311 }
312
313 public static Context getContext() {
314 return mSingleton;
315 }
316
317 /**
318 * @return result of getSystemService(name) but executed on UI thread.
319 */
320 public Object getSystemServiceFromUiThread(final String name) {
321 final Object lock = new Object();
322 final Object[] results = new Object[2]; // array for writable variables
323 synchronized (lock) {
324 runOnUiThread(new Runnable() {
325 @Override
326 public void run() {
327 synchronized (lock) {
328 results[0] = getSystemService(name);
329 results[1] = Boolean.TRUE;
330 lock.notify();
331 }
332 }
333 });
334 if (results[1] == null) {
335 try {
336 lock.wait();
337 } catch (InterruptedException ex) {
338 ex.printStackTrace();
339 }
340 }
341 }
342 return results[0];
343 }
344
345 static class ShowTextInputTask implements Runnable {
346 /*
347 * This is used to regulate the pan&scan method to have some offset from
348 * the bottom edge of the input region and the top edge of an input
349 * method (soft keyboard)
350 */
351 static final int HEIGHT_PADDING = 15;
352
353 public int x, y, w, h;
354
355 public ShowTextInputTask(int x, int y, int w, int h) {
356 this.x = x;
357 this.y = y;
358 this.w = w;
359 this.h = h;
360 }
361
362 @Override
363 public void run() {
364 AbsoluteLayout.LayoutParams params = new AbsoluteLayout.LayoutParams(
365 w, h + HEIGHT_PADDING, x, y);
366
367 if (mTextEdit == null) {
368 mTextEdit = new DummyEdit(getContext());
369
370 mLayout.addView(mTextEdit, params);
371 } else {
372 mTextEdit.setLayoutParams(params);
373 }
374
375 mTextEdit.setVisibility(View.VISIBLE);
376 mTextEdit.requestFocus();
377
378 InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
379 imm.showSoftInput(mTextEdit, 0);
380 }
381 }
382
383 public static boolean showTextInput(int x, int y, int w, int h) {
384 // Transfer the task to the main thread as a Runnable
385 return mSingleton.commandHandler.post(new ShowTextInputTask(x, y, w, h));
386 }
387
388 public static Surface getNativeSurface() {
389 return SDLActivity.mSurface.getNativeSurface();
390 }
391
392 // Audio
393 public static int audioInit(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames) {
394 int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO : AudioFormat.CHANNEL_CONFIGURATION_MONO;
395 int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT;
396 int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1);
397
398 Log.v("SDL", "SDL audio: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + (sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer");
399
400 // Let the user pick a larger buffer if they really want -- but ye
401 // gods they probably shouldn't, the minimums are horrifyingly high
402 // latency already
403 desiredFrames = Math.max(desiredFrames, (AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize);
404
405 if (mAudioTrack == null) {
406 mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate,
407 channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM);
408
409 // Instantiating AudioTrack can "succeed" without an exception and the track may still be invalid
410 // Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java
411 // Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState()
412
413 if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
414 Log.e("SDL", "Failed during initialization of Audio Track");
415 mAudioTrack = null;
416 return -1;
417 }
418
419 mAudioTrack.play();
420 }
421
422 Log.v("SDL", "SDL audio: got " + ((mAudioTrack.getChannelCount() >= 2) ? "stereo" : "mono") + " " + ((mAudioTrack.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit") + " " + (mAudioTrack.getSampleRate() / 1000f) + "kHz, " + desiredFrames + " frames buffer");
423
424 return 0;
425 }
426
427 public static void audioWriteShortBuffer(short[] buffer) {
428 for (int i = 0; i < buffer.length; ) {
429 int result = mAudioTrack.write(buffer, i, buffer.length - i);
430 if (result > 0) {
431 i += result;
432 } else if (result == 0) {
433 try {
434 Thread.sleep(1);
435 } catch(InterruptedException e) {
436 // Nom nom
437 }
438 } else {
439 Log.w("SDL", "SDL audio: error return from write(short)");
440 return;
441 }
442 }
443 }
444
445 public static void audioWriteByteBuffer(byte[] buffer) {
446 for (int i = 0; i < buffer.length; ) {
447 int result = mAudioTrack.write(buffer, i, buffer.length - i);
448 if (result > 0) {
449 i += result;
450 } else if (result == 0) {
451 try {
452 Thread.sleep(1);
453 } catch(InterruptedException e) {
454 // Nom nom
455 }
456 } else {
457 Log.w("SDL", "SDL audio: error return from write(byte)");
458 return;
459 }
460 }
461 }
462
463 public static void audioQuit() {
464 if (mAudioTrack != null) {
465 mAudioTrack.stop();
466 mAudioTrack = null;
467 }
468 }
469
470 // Input
471
472 /**
473 * @return an array which may be empty but is never null.
474 */
475 public static int[] inputGetInputDeviceIds(int sources) {
476 int[] ids = InputDevice.getDeviceIds();
477 int[] filtered = new int[ids.length];
478 int used = 0;
479 for (int i = 0; i < ids.length; ++i) {
480 InputDevice device = InputDevice.getDevice(ids[i]);
481 if ((device != null) && ((device.getSources() & sources) != 0)) {
482 filtered[used++] = device.getId();
483 }
484 }
485 return Arrays.copyOf(filtered, used);
486 }
487
488 // Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance
489 public static boolean handleJoystickMotionEvent(MotionEvent event) {
490 return mJoystickHandler.handleMotionEvent(event);
491 }
492
493 public static void pollInputDevices() {
494 if (SDLActivity.mSDLThread != null) {
495 mJoystickHandler.pollInputDevices();
496 }
497 }
498
499 }
500
501 /**
502 Simple nativeInit() runnable
503 */
504 class SDLMain implements Runnable {
505 @Override
506 public void run() {
507 // Runs SDL_main()
508 SDLActivity.nativeInit();
509
510 //Log.v("SDL", "SDL thread terminated");
511 }
512 }
513
514
515 /**
516 SDLSurface. This is what we draw on, so we need to know when it's created
517 in order to do anything useful.
518
519 Because of this, that's where we set up the SDL thread
520 */
521 class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
522 View.OnKeyListener, View.OnTouchListener, SensorEventListener {
523
524 // Sensors
525 protected static SensorManager mSensorManager;
526 protected static Display mDisplay;
527
528 // Keep track of the surface size to normalize touch events
529 protected static float mWidth, mHeight;
530
531 // Startup
532 public SDLSurface(Context context) {
533 super(context);
534 getHolder().addCallback(this);
535
536 setFocusable(true);
537 setFocusableInTouchMode(true);
538 requestFocus();
539 setOnKeyListener(this);
540 setOnTouchListener(this);
541
542 mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
543 mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
544
545 if(Build.VERSION.SDK_INT >= 12) {
546 setOnGenericMotionListener(new SDLGenericMotionListener_API12());
547 }
548
549 // Some arbitrary defaults to avoid a potential division by zero
550 mWidth = 1.0f;
551 mHeight = 1.0f;
552 }
553
554 public Surface getNativeSurface() {
555 return getHolder().getSurface();
556 }
557
558 // Called when we have a valid drawing surface
559 @Override
560 public void surfaceCreated(SurfaceHolder holder) {
561 Log.v("SDL", "surfaceCreated()");
562 holder.setType(SurfaceHolder.SURFACE_TYPE_GPU);
563 }
564
565 // Called when we lose the surface
566 @Override
567 public void surfaceDestroyed(SurfaceHolder holder) {
568 Log.v("SDL", "surfaceDestroyed()");
569 // Call this *before* setting mIsSurfaceReady to 'false'
570 SDLActivity.handlePause();
571 SDLActivity.mIsSurfaceReady = false;
572 SDLActivity.onNativeSurfaceDestroyed();
573 }
574
575 // Called when the surface is resized
576 @Override
577 public void surfaceChanged(SurfaceHolder holder,
578 int format, int width, int height) {
579 Log.v("SDL", "surfaceChanged()");
580
581 int sdlFormat = 0x15151002; // SDL_PIXELFORMAT_RGB565 by default
582 switch (format) {
583 case PixelFormat.A_8:
584 Log.v("SDL", "pixel format A_8");
585 break;
586 case PixelFormat.LA_88:
587 Log.v("SDL", "pixel format LA_88");
588 break;
589 case PixelFormat.L_8:
590 Log.v("SDL", "pixel format L_8");
591 break;
592 case PixelFormat.RGBA_4444:
593 Log.v("SDL", "pixel format RGBA_4444");
594 sdlFormat = 0x15421002; // SDL_PIXELFORMAT_RGBA4444
595 break;
596 case PixelFormat.RGBA_5551:
597 Log.v("SDL", "pixel format RGBA_5551");
598 sdlFormat = 0x15441002; // SDL_PIXELFORMAT_RGBA5551
599 break;
600 case PixelFormat.RGBA_8888:
601 Log.v("SDL", "pixel format RGBA_8888");
602 sdlFormat = 0x16462004; // SDL_PIXELFORMAT_RGBA8888
603 break;
604 case PixelFormat.RGBX_8888:
605 Log.v("SDL", "pixel format RGBX_8888");
606 sdlFormat = 0x16261804; // SDL_PIXELFORMAT_RGBX8888
607 break;
608 case PixelFormat.RGB_332:
609 Log.v("SDL", "pixel format RGB_332");
610 sdlFormat = 0x14110801; // SDL_PIXELFORMAT_RGB332
611 break;
612 case PixelFormat.RGB_565:
613 Log.v("SDL", "pixel format RGB_565");
614 sdlFormat = 0x15151002; // SDL_PIXELFORMAT_RGB565
615 break;
616 case PixelFormat.RGB_888:
617 Log.v("SDL", "pixel format RGB_888");
618 // Not sure this is right, maybe SDL_PIXELFORMAT_RGB24 instead?
619 sdlFormat = 0x16161804; // SDL_PIXELFORMAT_RGB888
620 break;
621 default:
622 Log.v("SDL", "pixel format unknown " + format);
623 break;
624 }
625
626 mWidth = width;
627 mHeight = height;
628 SDLActivity.onNativeResize(width, height, sdlFormat);
629 Log.v("SDL", "Window size:" + width + "x"+height);
630
631 // Set mIsSurfaceReady to 'true' *before* making a call to handleResume
632 SDLActivity.mIsSurfaceReady = true;
633 SDLActivity.onNativeSurfaceChanged();
634
635
636 if (SDLActivity.mSDLThread == null) {
637 // This is the entry point to the C app.
638 // Start up the C app thread and enable sensor input for the first time
639
640 SDLActivity.mSDLThread = new Thread(new SDLMain(), "SDLThread");
641 enableSensor(Sensor.TYPE_ACCELEROMETER, true);
642 SDLActivity.mSDLThread.start();
643
644 // Set up a listener thread to catch when the native thread ends
645 new Thread(new Runnable(){
646 @Override
647 public void run(){
648 try {
649 SDLActivity.mSDLThread.join();
650 }
651 catch(Exception e){}
652 finally{
653 // Native thread has finished
654 if (! SDLActivity.mExitCalledFromJava) {
655 SDLActivity.handleNativeExit();
656 }
657 }
658 }
659 }).start();
660 }
661 }
662
663 // unused
664 @Override
665 public void onDraw(Canvas canvas) {}
666
667
668 // Key events
669 @Override
670 public boolean onKey(View v, int keyCode, KeyEvent event) {
671 // Dispatch the different events depending on where they come from
672 // Some SOURCE_DPAD or SOURCE_GAMEPAD are also SOURCE_KEYBOARD
673 // So, we try to process them as DPAD or GAMEPAD events first, if that fails we try them as KEYBOARD
674
675 if ( (event.getSource() & 0x00000401) != 0 || /* API 12: SOURCE_GAMEPAD */
676 (event.getSource() & InputDevice.SOURCE_DPAD) != 0 ) {
677 if (event.getAction() == KeyEvent.ACTION_DOWN) {
678 if (SDLActivity.onNativePadDown(event.getDeviceId(), keyCode) == 0) {
679 return true;
680 }
681 } else if (event.getAction() == KeyEvent.ACTION_UP) {
682 if (SDLActivity.onNativePadUp(event.getDeviceId(), keyCode) == 0) {
683 return true;
684 }
685 }
686 }
687
688 if( (event.getSource() & InputDevice.SOURCE_KEYBOARD) != 0) {
689 if (event.getAction() == KeyEvent.ACTION_DOWN) {
690 //Log.v("SDL", "key down: " + keyCode);
691 SDLActivity.onNativeKeyDown(keyCode);
692 return true;
693 }
694 else if (event.getAction() == KeyEvent.ACTION_UP) {
695 //Log.v("SDL", "key up: " + keyCode);
696 SDLActivity.onNativeKeyUp(keyCode);
697 return true;
698 }
699 }
700
701 return false;
702 }
703
704 // Touch events
705 @Override
706 public boolean onTouch(View v, MotionEvent event) {
707 /* Ref: http://developer.android.com/training/gestures/multi.html */
708 final int touchDevId = event.getDeviceId();
709 final int pointerCount = event.getPointerCount();
710 int action = event.getActionMasked();
711 int pointerFingerId;
712 int i = -1;
713 float x,y,p;
714
715 switch(action) {
716 case MotionEvent.ACTION_MOVE:
717 for (i = 0; i < pointerCount; i++) {
718 pointerFingerId = event.getPointerId(i);
719 x = event.getX(i) / mWidth;
720 y = event.getY(i) / mHeight;
721 p = event.getPressure(i);
722 SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
723 }
724 break;
725
726 case MotionEvent.ACTION_UP:
727 case MotionEvent.ACTION_DOWN:
728 // Primary pointer up/down, the index is always zero
729 i = 0;
730 case MotionEvent.ACTION_POINTER_UP:
731 case MotionEvent.ACTION_POINTER_DOWN:
732 // Non primary pointer up/down
733 if (i == -1) {
734 i = event.getActionIndex();
735 }
736
737 pointerFingerId = event.getPointerId(i);
738 x = event.getX(i) / mWidth;
739 y = event.getY(i) / mHeight;
740 p = event.getPressure(i);
741 SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
742 break;
743
744 default:
745 break;
746 }
747
748 return true;
749 }
750
751 // Sensor events
752 public void enableSensor(int sensortype, boolean enabled) {
753 // TODO: This uses getDefaultSensor - what if we have >1 accels?
754 if (enabled) {
755 mSensorManager.registerListener(this,
756 mSensorManager.getDefaultSensor(sensortype),
757 SensorManager.SENSOR_DELAY_GAME, null);
758 } else {
759 mSensorManager.unregisterListener(this,
760 mSensorManager.getDefaultSensor(sensortype));
761 }
762 }
763
764 @Override
765 public void onAccuracyChanged(Sensor sensor, int accuracy) {
766 // TODO
767 }
768
769 @Override
770 public void onSensorChanged(SensorEvent event) {
771 if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
772 float x, y;
773 switch (mDisplay.getRotation()) {
774 case Surface.ROTATION_90:
775 x = -event.values[1];
776 y = event.values[0];
777 break;
778 case Surface.ROTATION_270:
779 x = event.values[1];
780 y = -event.values[0];
781 break;
782 case Surface.ROTATION_180:
783 x = -event.values[1];
784 y = -event.values[0];
785 break;
786 default:
787 x = event.values[0];
788 y = event.values[1];
789 break;
790 }
791 SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH,
792 y / SensorManager.GRAVITY_EARTH,
793 event.values[2] / SensorManager.GRAVITY_EARTH - 1);
794 }
795 }
796 }
797
798 /* This is a fake invisible editor view that receives the input and defines the
799 * pan&scan region
800 */
801 class DummyEdit extends View implements View.OnKeyListener {
802 InputConnection ic;
803
804 public DummyEdit(Context context) {
805 super(context);
806 setFocusableInTouchMode(true);
807 setFocusable(true);
808 setOnKeyListener(this);
809 }
810
811 @Override
812 public boolean onCheckIsTextEditor() {
813 return true;
814 }
815
816 @Override
817 public boolean onKey(View v, int keyCode, KeyEvent event) {
818
819 // This handles the hardware keyboard input
820 if (event.isPrintingKey()) {
821 if (event.getAction() == KeyEvent.ACTION_DOWN) {
822 ic.commitText(String.valueOf((char) event.getUnicodeChar()), 1);
823 }
824 return true;
825 }
826
827 if (event.getAction() == KeyEvent.ACTION_DOWN) {
828 SDLActivity.onNativeKeyDown(keyCode);
829 return true;
830 } else if (event.getAction() == KeyEvent.ACTION_UP) {
831 SDLActivity.onNativeKeyUp(keyCode);
832 return true;
833 }
834
835 return false;
836 }
837
838 //
839 @Override
840 public boolean onKeyPreIme (int keyCode, KeyEvent event) {
841 // As seen on StackOverflow: http://stackoverflow.com/questions/7634346/keyboard-hide-event
842 // FIXME: Discussion at http://bugzilla.libsdl.org/show_bug.cgi?id=1639
843 // FIXME: This is not a 100% effective solution to the problem of detecting if the keyboard is showing or not
844 // FIXME: A more effective solution would be to change our Layout from AbsoluteLayout to Relative or Linear
845 // FIXME: And determine the keyboard presence doing this: http://stackoverflow.com/questions/2150078/how-to-check-visibility-of-software-keyboard-in-android
846 // FIXME: An even more effective way would be if Android provided this out of the box, but where would the fun be in that :)
847 if (event.getAction()==KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
848 if (SDLActivity.mTextEdit != null && SDLActivity.mTextEdit.getVisibility() == View.VISIBLE) {
849 SDLActivity.onNativeKeyboardFocusLost();
850 }
851 }
852 return super.onKeyPreIme(keyCode, event);
853 }
854
855 @Override
856 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
857 ic = new SDLInputConnection(this, true);
858
859 outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI
860 | 33554432 /* API 11: EditorInfo.IME_FLAG_NO_FULLSCREEN */;
861
862 return ic;
863 }
864 }
865
866 class SDLInputConnection extends BaseInputConnection {
867
868 public SDLInputConnection(View targetView, boolean fullEditor) {
869 super(targetView, fullEditor);
870
871 }
872
873 @Override
874 public boolean sendKeyEvent(KeyEvent event) {
875
876 /*
877 * This handles the keycodes from soft keyboard (and IME-translated
878 * input from hardkeyboard)
879 */
880 int keyCode = event.getKeyCode();
881 if (event.getAction() == KeyEvent.ACTION_DOWN) {
882 if (event.isPrintingKey()) {
883 commitText(String.valueOf((char) event.getUnicodeChar()), 1);
884 }
885 SDLActivity.onNativeKeyDown(keyCode);
886 return true;
887 } else if (event.getAction() == KeyEvent.ACTION_UP) {
888
889 SDLActivity.onNativeKeyUp(keyCode);
890 return true;
891 }
892 return super.sendKeyEvent(event);
893 }
894
895 @Override
896 public boolean commitText(CharSequence text, int newCursorPosition) {
897
898 nativeCommitText(text.toString(), newCursorPosition);
899
900 return super.commitText(text, newCursorPosition);
901 }
902
903 @Override
904 public boolean setComposingText(CharSequence text, int newCursorPosition) {
905
906 nativeSetComposingText(text.toString(), newCursorPosition);
907
908 return super.setComposingText(text, newCursorPosition);
909 }
910
911 public native void nativeCommitText(String text, int newCursorPosition);
912
913 public native void nativeSetComposingText(String text, int newCursorPosition);
914
915 @Override
916 public boolean deleteSurroundingText(int beforeLength, int afterLength) {
917 // Workaround to capture backspace key. Ref: http://stackoverflow.com/questions/14560344/android-backspace-in-webview-baseinputconnection
918 if (beforeLength == 1 && afterLength == 0) {
919 // backspace
920 return super.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))
921 && super.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));
922 }
923
924 return super.deleteSurroundingText(beforeLength, afterLength);
925 }
926 }
927
928 /* A null joystick handler for API level < 12 devices (the accelerometer is handled separately) */
929 class SDLJoystickHandler {
930
931 public boolean handleMotionEvent(MotionEvent event) {
932 return false;
933 }
934
935 public void pollInputDevices() {
936 }
937 }
938
939 /* Actual joystick functionality available for API >= 12 devices */
940 class SDLJoystickHandler_API12 extends SDLJoystickHandler {
941
942 class SDLJoystick {
943 public int device_id;
944 public String name;
945 public ArrayList<InputDevice.MotionRange> axes;
946 public ArrayList<InputDevice.MotionRange> hats;
947 }
948 class RangeComparator implements Comparator<InputDevice.MotionRange>
949 {
950 @Override
951 public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) {
952 return arg0.getAxis() - arg1.getAxis();
953 }
954 }
955
956 private ArrayList<SDLJoystick> mJoysticks;
957
958 public SDLJoystickHandler_API12() {
959
960 mJoysticks = new ArrayList<SDLJoystick>();
961 }
962
963 @Override
964 public void pollInputDevices() {
965 int[] deviceIds = InputDevice.getDeviceIds();
966 // It helps processing the device ids in reverse order
967 // For example, in the case of the XBox 360 wireless dongle,
968 // so the first controller seen by SDL matches what the receiver
969 // considers to be the first controller
970
971 for(int i=deviceIds.length-1; i>-1; i--) {
972 SDLJoystick joystick = getJoystick(deviceIds[i]);
973 if (joystick == null) {
974 joystick = new SDLJoystick();
975 InputDevice joystickDevice = InputDevice.getDevice(deviceIds[i]);
976 if( (joystickDevice.getSources() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
977 joystick.device_id = deviceIds[i];
978 joystick.name = joystickDevice.getName();
979 joystick.axes = new ArrayList<InputDevice.MotionRange>();
980 joystick.hats = new ArrayList<InputDevice.MotionRange>();
981
982 List<InputDevice.MotionRange> ranges = joystickDevice.getMotionRanges();
983 Collections.sort(ranges, new RangeComparator());
984 for (InputDevice.MotionRange range : ranges ) {
985 if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0 ) {
986 if (range.getAxis() == MotionEvent.AXIS_HAT_X ||
987 range.getAxis() == MotionEvent.AXIS_HAT_Y) {
988 joystick.hats.add(range);
989 }
990 else {
991 joystick.axes.add(range);
992 }
993 }
994 }
995
996 mJoysticks.add(joystick);
997 SDLActivity.nativeAddJoystick(joystick.device_id, joystick.name, 0, -1,
998 joystick.axes.size(), joystick.hats.size()/2, 0);
999 }
1000 }
1001 }
1002
1003 /* Check removed devices */
1004 ArrayList<Integer> removedDevices = new ArrayList<Integer>();
1005 for(int i=0; i < mJoysticks.size(); i++) {
1006 int device_id = mJoysticks.get(i).device_id;
1007 int j;
1008 for (j=0; j < deviceIds.length; j++) {
1009 if (device_id == deviceIds[j]) break;
1010 }
1011 if (j == deviceIds.length) {
1012 removedDevices.add(device_id);
1013 }
1014 }
1015
1016 for(int i=0; i < removedDevices.size(); i++) {
1017 int device_id = removedDevices.get(i);
1018 SDLActivity.nativeRemoveJoystick(device_id);
1019 for (int j=0; j < mJoysticks.size(); j++) {
1020 if (mJoysticks.get(j).device_id == device_id) {
1021 mJoysticks.remove(j);
1022 break;
1023 }
1024 }
1025 }
1026 }
1027
1028 protected SDLJoystick getJoystick(int device_id) {
1029 for(int i=0; i < mJoysticks.size(); i++) {
1030 if (mJoysticks.get(i).device_id == device_id) {
1031 return mJoysticks.get(i);
1032 }
1033 }
1034 return null;
1035 }
1036
1037 @Override
1038 public boolean handleMotionEvent(MotionEvent event) {
1039 if ( (event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0) {
1040 int actionPointerIndex = event.getActionIndex();
1041 int action = event.getActionMasked();
1042 switch(action) {
1043 case MotionEvent.ACTION_MOVE:
1044 SDLJoystick joystick = getJoystick(event.getDeviceId());
1045 if ( joystick != null ) {
1046 for (int i = 0; i < joystick.axes.size(); i++) {
1047 InputDevice.MotionRange range = joystick.axes.get(i);
1048 /* Normalize the value to -1...1 */
1049 float value = ( event.getAxisValue( range.getAxis(), actionPointerIndex) - range.getMin() ) / range.getRange() * 2.0f - 1.0f;
1050 SDLActivity.onNativeJoy(joystick.device_id, i, value );
1051 }
1052 for (int i = 0; i < joystick.hats.size(); i+=2) {
1053 int hatX = Math.round(event.getAxisValue( joystick.hats.get(i).getAxis(), actionPointerIndex ) );
1054 int hatY = Math.round(event.getAxisValue( joystick.hats.get(i+1).getAxis(), actionPointerIndex ) );
1055 SDLActivity.onNativeHat(joystick.device_id, i/2, hatX, hatY );
1056 }
1057 }
1058 break;
1059 default:
1060 break;
1061 }
1062 }
1063 return true;
1064 }
1065 }
1066
1067 class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener {
1068 // Generic Motion (mouse hover, joystick...) events go here
1069 // We only have joysticks yet
1070 @Override
1071 public boolean onGenericMotion(View v, MotionEvent event) {
1072 return SDLActivity.handleJoystickMotionEvent(event);
1073 }
1074 }