Mercurial > repos > blastem
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 } |