OLD | NEW |
| (Empty) |
1 /* | |
2 * SDL License | |
3 * This software is provided 'as-is', without any express or implied | |
4 * warranty. In no event will the authors be held liable for any damages | |
5 * arising from the use of this software. | |
6 | |
7 * Permission is granted to anyone to use this software for any purpose, | |
8 * including commercial applications, and to alter it and redistribute it | |
9 * freely, subject to the following restrictions: | |
10 * | |
11 * 1. The origin of this software must not be misrepresented; you must not | |
12 * claim that you wrote the original software. If you use this software | |
13 * in a product, an acknowledgment in the product documentation would be | |
14 * appreciated but is not required. | |
15 * 2. Altered source versions must be plainly marked as such, and must not be | |
16 * misrepresented as being the original software. | |
17 * 3. This notice may not be removed or altered from any source distribution. | |
18 * | |
19 */ | |
20 | |
21 // This file comes from SDL, but it has been modified to pull Skia's libraries | |
22 | |
23 package org.libsdl.app; | |
24 | |
25 import java.io.IOException; | |
26 import java.io.InputStream; | |
27 import java.util.ArrayList; | |
28 import java.util.Arrays; | |
29 import java.util.Collections; | |
30 import java.util.Comparator; | |
31 import java.util.List; | |
32 import java.lang.reflect.Method; | |
33 | |
34 import android.app.*; | |
35 import android.content.*; | |
36 import android.text.InputType; | |
37 import android.view.*; | |
38 import android.view.inputmethod.BaseInputConnection; | |
39 import android.view.inputmethod.EditorInfo; | |
40 import android.view.inputmethod.InputConnection; | |
41 import android.view.inputmethod.InputMethodManager; | |
42 import android.widget.AbsoluteLayout; | |
43 import android.widget.Button; | |
44 import android.widget.LinearLayout; | |
45 import android.widget.TextView; | |
46 import android.os.*; | |
47 import android.util.Log; | |
48 import android.util.SparseArray; | |
49 import android.graphics.*; | |
50 import android.graphics.drawable.Drawable; | |
51 import android.media.*; | |
52 import android.hardware.*; | |
53 import android.content.pm.ActivityInfo; | |
54 | |
55 /** | |
56 SDL Activity | |
57 */ | |
58 public class SDLActivity extends Activity { | |
59 private static final String TAG = "SDL"; | |
60 | |
61 // Keep track of the paused state | |
62 public static boolean mIsPaused, mIsSurfaceReady, mHasFocus; | |
63 public static boolean mExitCalledFromJava; | |
64 | |
65 /** If shared libraries (e.g. SDL or the native application) could not be lo
aded. */ | |
66 public static boolean mBrokenLibraries; | |
67 | |
68 // If we want to separate mouse and touch events. | |
69 // This is only toggled in native code when a hint is set! | |
70 public static boolean mSeparateMouseAndTouch; | |
71 | |
72 // Main components | |
73 protected static SDLActivity mSingleton; | |
74 protected static SDLSurface mSurface; | |
75 protected static View mTextEdit; | |
76 protected static ViewGroup mLayout; | |
77 protected static SDLJoystickHandler mJoystickHandler; | |
78 | |
79 // This is what SDL runs in. It invokes SDL_main(), eventually | |
80 protected static Thread mSDLThread; | |
81 | |
82 // Audio | |
83 protected static AudioTrack mAudioTrack; | |
84 | |
85 /** | |
86 * This method is called by SDL before loading the native shared libraries. | |
87 * It can be overridden to provide names of shared libraries to be loaded. | |
88 * The default implementation returns the defaults. It never returns null. | |
89 * An array returned by a new implementation must at least contain "SDL2". | |
90 * Also keep in mind that the order the libraries are loaded may matter. | |
91 * @return names of shared libraries to be loaded (e.g. "SDL2", "main"). | |
92 */ | |
93 protected String[] getLibraries() { | |
94 return new String[] { | |
95 //"SDL2", | |
96 // "SDL2_image", | |
97 // "SDL2_mixer", | |
98 // "SDL2_net", | |
99 // "SDL2_ttf", | |
100 "skia_android", | |
101 "visualbench" | |
102 }; | |
103 } | |
104 | |
105 // Load the .so | |
106 public void loadLibraries() { | |
107 for (String lib : getLibraries()) { | |
108 System.loadLibrary(lib); | |
109 } | |
110 } | |
111 | |
112 /** | |
113 * This method is called by SDL before starting the native application threa
d. | |
114 * It can be overridden to provide the arguments after the application name. | |
115 * The default implementation returns an empty array. It never returns null. | |
116 * @return arguments for the native application. | |
117 */ | |
118 protected String[] getArguments() { | |
119 return new String[0]; | |
120 } | |
121 | |
122 public static void initialize() { | |
123 // The static nature of the singleton and Android quirkyness force us to
initialize everything here | |
124 // Otherwise, when exiting the app and returning to it, these variables
*keep* their pre exit values | |
125 mSingleton = null; | |
126 mSurface = null; | |
127 mTextEdit = null; | |
128 mLayout = null; | |
129 mJoystickHandler = null; | |
130 mSDLThread = null; | |
131 mAudioTrack = null; | |
132 mExitCalledFromJava = false; | |
133 mBrokenLibraries = false; | |
134 mIsPaused = false; | |
135 mIsSurfaceReady = false; | |
136 mHasFocus = true; | |
137 } | |
138 | |
139 // Setup | |
140 @Override | |
141 protected void onCreate(Bundle savedInstanceState) { | |
142 Log.v(TAG, "Device: " + android.os.Build.DEVICE); | |
143 Log.v(TAG, "Model: " + android.os.Build.MODEL); | |
144 Log.v(TAG, "onCreate(): " + mSingleton); | |
145 super.onCreate(savedInstanceState); | |
146 | |
147 SDLActivity.initialize(); | |
148 // So we can call stuff from static callbacks | |
149 mSingleton = this; | |
150 | |
151 // Load shared libraries | |
152 String errorMsgBrokenLib = ""; | |
153 try { | |
154 loadLibraries(); | |
155 } catch(UnsatisfiedLinkError e) { | |
156 System.err.println(e.getMessage()); | |
157 mBrokenLibraries = true; | |
158 errorMsgBrokenLib = e.getMessage(); | |
159 } catch(Exception e) { | |
160 System.err.println(e.getMessage()); | |
161 mBrokenLibraries = true; | |
162 errorMsgBrokenLib = e.getMessage(); | |
163 } | |
164 | |
165 if (mBrokenLibraries) | |
166 { | |
167 AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this); | |
168 dlgAlert.setMessage("An error occurred while trying to start the app
lication. Please try again and/or reinstall." | |
169 + System.getProperty("line.separator") | |
170 + System.getProperty("line.separator") | |
171 + "Error: " + errorMsgBrokenLib); | |
172 dlgAlert.setTitle("SDL Error"); | |
173 dlgAlert.setPositiveButton("Exit", | |
174 new DialogInterface.OnClickListener() { | |
175 @Override | |
176 public void onClick(DialogInterface dialog,int id) { | |
177 // if this button is clicked, close current activity | |
178 SDLActivity.mSingleton.finish(); | |
179 } | |
180 }); | |
181 dlgAlert.setCancelable(false); | |
182 dlgAlert.create().show(); | |
183 | |
184 return; | |
185 } | |
186 | |
187 // Set up the surface | |
188 mSurface = new SDLSurface(getApplication()); | |
189 | |
190 if(Build.VERSION.SDK_INT >= 12) { | |
191 mJoystickHandler = new SDLJoystickHandler_API12(); | |
192 } | |
193 else { | |
194 mJoystickHandler = new SDLJoystickHandler(); | |
195 } | |
196 | |
197 mLayout = new AbsoluteLayout(this); | |
198 mLayout.addView(mSurface); | |
199 | |
200 setContentView(mLayout); | |
201 | |
202 // Get filename from "Open with" of another application | |
203 Intent intent = getIntent(); | |
204 | |
205 if (intent != null && intent.getData() != null) { | |
206 String filename = intent.getData().getPath(); | |
207 if (filename != null) { | |
208 Log.v(TAG, "Got filename: " + filename); | |
209 SDLActivity.onNativeDropFile(filename); | |
210 } | |
211 } | |
212 } | |
213 | |
214 // Events | |
215 @Override | |
216 protected void onPause() { | |
217 Log.v(TAG, "onPause()"); | |
218 super.onPause(); | |
219 | |
220 if (SDLActivity.mBrokenLibraries) { | |
221 return; | |
222 } | |
223 | |
224 SDLActivity.handlePause(); | |
225 } | |
226 | |
227 @Override | |
228 protected void onResume() { | |
229 Log.v(TAG, "onResume()"); | |
230 super.onResume(); | |
231 | |
232 if (SDLActivity.mBrokenLibraries) { | |
233 return; | |
234 } | |
235 | |
236 SDLActivity.handleResume(); | |
237 } | |
238 | |
239 | |
240 @Override | |
241 public void onWindowFocusChanged(boolean hasFocus) { | |
242 super.onWindowFocusChanged(hasFocus); | |
243 Log.v(TAG, "onWindowFocusChanged(): " + hasFocus); | |
244 | |
245 if (SDLActivity.mBrokenLibraries) { | |
246 return; | |
247 } | |
248 | |
249 SDLActivity.mHasFocus = hasFocus; | |
250 if (hasFocus) { | |
251 SDLActivity.handleResume(); | |
252 } | |
253 } | |
254 | |
255 @Override | |
256 public void onLowMemory() { | |
257 Log.v(TAG, "onLowMemory()"); | |
258 super.onLowMemory(); | |
259 | |
260 if (SDLActivity.mBrokenLibraries) { | |
261 return; | |
262 } | |
263 | |
264 SDLActivity.nativeLowMemory(); | |
265 } | |
266 | |
267 @Override | |
268 protected void onDestroy() { | |
269 Log.v(TAG, "onDestroy()"); | |
270 | |
271 if (SDLActivity.mBrokenLibraries) { | |
272 super.onDestroy(); | |
273 // Reset everything in case the user re opens the app | |
274 SDLActivity.initialize(); | |
275 return; | |
276 } | |
277 | |
278 // Send a quit message to the application | |
279 SDLActivity.mExitCalledFromJava = true; | |
280 SDLActivity.nativeQuit(); | |
281 | |
282 // Now wait for the SDL thread to quit | |
283 if (SDLActivity.mSDLThread != null) { | |
284 try { | |
285 SDLActivity.mSDLThread.join(); | |
286 } catch(Exception e) { | |
287 Log.v(TAG, "Problem stopping thread: " + e); | |
288 } | |
289 SDLActivity.mSDLThread = null; | |
290 | |
291 //Log.v(TAG, "Finished waiting for SDL thread"); | |
292 } | |
293 | |
294 super.onDestroy(); | |
295 // Reset everything in case the user re opens the app | |
296 SDLActivity.initialize(); | |
297 } | |
298 | |
299 @Override | |
300 public boolean dispatchKeyEvent(KeyEvent event) { | |
301 | |
302 if (SDLActivity.mBrokenLibraries) { | |
303 return false; | |
304 } | |
305 | |
306 int keyCode = event.getKeyCode(); | |
307 // Ignore certain special keys so they're handled by Android | |
308 if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || | |
309 keyCode == KeyEvent.KEYCODE_VOLUME_UP || | |
310 keyCode == KeyEvent.KEYCODE_CAMERA || | |
311 keyCode == 168 || /* API 11: KeyEvent.KEYCODE_ZOOM_IN */ | |
312 keyCode == 169 /* API 11: KeyEvent.KEYCODE_ZOOM_OUT */ | |
313 ) { | |
314 return false; | |
315 } | |
316 return super.dispatchKeyEvent(event); | |
317 } | |
318 | |
319 /** Called by onPause or surfaceDestroyed. Even if surfaceDestroyed | |
320 * is the first to be called, mIsSurfaceReady should still be set | |
321 * to 'true' during the call to onPause (in a usual scenario). | |
322 */ | |
323 public static void handlePause() { | |
324 if (!SDLActivity.mIsPaused && SDLActivity.mIsSurfaceReady) { | |
325 SDLActivity.mIsPaused = true; | |
326 SDLActivity.nativePause(); | |
327 mSurface.handlePause(); | |
328 } | |
329 } | |
330 | |
331 /** Called by onResume or surfaceCreated. An actual resume should be done on
ly when the surface is ready. | |
332 * Note: Some Android variants may send multiple surfaceChanged events, so w
e don't need to resume | |
333 * every time we get one of those events, only if it comes after surfaceDest
royed | |
334 */ | |
335 public static void handleResume() { | |
336 if (SDLActivity.mIsPaused && SDLActivity.mIsSurfaceReady && SDLActivity.
mHasFocus) { | |
337 SDLActivity.mIsPaused = false; | |
338 SDLActivity.nativeResume(); | |
339 mSurface.handleResume(); | |
340 } | |
341 } | |
342 | |
343 /* The native thread has finished */ | |
344 public static void handleNativeExit() { | |
345 SDLActivity.mSDLThread = null; | |
346 mSingleton.finish(); | |
347 } | |
348 | |
349 | |
350 // Messages from the SDLMain thread | |
351 static final int COMMAND_CHANGE_TITLE = 1; | |
352 static final int COMMAND_UNUSED = 2; | |
353 static final int COMMAND_TEXTEDIT_HIDE = 3; | |
354 static final int COMMAND_SET_KEEP_SCREEN_ON = 5; | |
355 | |
356 protected static final int COMMAND_USER = 0x8000; | |
357 | |
358 /** | |
359 * This method is called by SDL if SDL did not handle a message itself. | |
360 * This happens if a received message contains an unsupported command. | |
361 * Method can be overwritten to handle Messages in a different class. | |
362 * @param command the command of the message. | |
363 * @param param the parameter of the message. May be null. | |
364 * @return if the message was handled in overridden method. | |
365 */ | |
366 protected boolean onUnhandledMessage(int command, Object param) { | |
367 return false; | |
368 } | |
369 | |
370 /** | |
371 * A Handler class for Messages from native SDL applications. | |
372 * It uses current Activities as target (e.g. for the title). | |
373 * static to prevent implicit references to enclosing object. | |
374 */ | |
375 protected static class SDLCommandHandler extends Handler { | |
376 @Override | |
377 public void handleMessage(Message msg) { | |
378 Context context = getContext(); | |
379 if (context == null) { | |
380 Log.e(TAG, "error handling message, getContext() returned null")
; | |
381 return; | |
382 } | |
383 switch (msg.arg1) { | |
384 case COMMAND_CHANGE_TITLE: | |
385 if (context instanceof Activity) { | |
386 ((Activity) context).setTitle((String)msg.obj); | |
387 } else { | |
388 Log.e(TAG, "error handling message, getContext() returned no
Activity"); | |
389 } | |
390 break; | |
391 case COMMAND_TEXTEDIT_HIDE: | |
392 if (mTextEdit != null) { | |
393 mTextEdit.setVisibility(View.GONE); | |
394 | |
395 InputMethodManager imm = (InputMethodManager) context.getSys
temService(Context.INPUT_METHOD_SERVICE); | |
396 imm.hideSoftInputFromWindow(mTextEdit.getWindowToken(), 0); | |
397 } | |
398 break; | |
399 case COMMAND_SET_KEEP_SCREEN_ON: | |
400 { | |
401 Window window = ((Activity) context).getWindow(); | |
402 if (window != null) { | |
403 if ((msg.obj instanceof Integer) && (((Integer) msg.obj).int
Value() != 0)) { | |
404 window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCR
EEN_ON); | |
405 } else { | |
406 window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_S
CREEN_ON); | |
407 } | |
408 } | |
409 break; | |
410 } | |
411 default: | |
412 if ((context instanceof SDLActivity) && !((SDLActivity) context)
.onUnhandledMessage(msg.arg1, msg.obj)) { | |
413 Log.e(TAG, "error handling message, command is " + msg.arg1)
; | |
414 } | |
415 } | |
416 } | |
417 } | |
418 | |
419 // Handler for the messages | |
420 Handler commandHandler = new SDLCommandHandler(); | |
421 | |
422 // Send a message from the SDLMain thread | |
423 boolean sendCommand(int command, Object data) { | |
424 Message msg = commandHandler.obtainMessage(); | |
425 msg.arg1 = command; | |
426 msg.obj = data; | |
427 return commandHandler.sendMessage(msg); | |
428 } | |
429 | |
430 // C functions we call | |
431 public static native int nativeInit(Object arguments); | |
432 public static native void nativeLowMemory(); | |
433 public static native void nativeQuit(); | |
434 public static native void nativePause(); | |
435 public static native void nativeResume(); | |
436 public static native void onNativeDropFile(String filename); | |
437 public static native void onNativeResize(int x, int y, int format, float rat
e); | |
438 public static native int onNativePadDown(int device_id, int keycode); | |
439 public static native int onNativePadUp(int device_id, int keycode); | |
440 public static native void onNativeJoy(int device_id, int axis, | |
441 float value); | |
442 public static native void onNativeHat(int device_id, int hat_id, | |
443 int x, int y); | |
444 public static native void onNativeKeyDown(int keycode); | |
445 public static native void onNativeKeyUp(int keycode); | |
446 public static native void onNativeKeyboardFocusLost(); | |
447 public static native void onNativeMouse(int button, int action, float x, flo
at y); | |
448 public static native void onNativeTouch(int touchDevId, int pointerFingerId, | |
449 int action, float x, | |
450 float y, float p); | |
451 public static native void onNativeAccel(float x, float y, float z); | |
452 public static native void onNativeSurfaceChanged(); | |
453 public static native void onNativeSurfaceDestroyed(); | |
454 public static native int nativeAddJoystick(int device_id, String name, | |
455 int is_accelerometer, int nbutton
s, | |
456 int naxes, int nhats, int nballs)
; | |
457 public static native int nativeRemoveJoystick(int device_id); | |
458 public static native String nativeGetHint(String name); | |
459 | |
460 /** | |
461 * This method is called by SDL using JNI. | |
462 */ | |
463 public static boolean setActivityTitle(String title) { | |
464 // Called from SDLMain() thread and can't directly affect the view | |
465 return mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title); | |
466 } | |
467 | |
468 /** | |
469 * This method is called by SDL using JNI. | |
470 */ | |
471 public static boolean sendMessage(int command, int param) { | |
472 return mSingleton.sendCommand(command, Integer.valueOf(param)); | |
473 } | |
474 | |
475 /** | |
476 * This method is called by SDL using JNI. | |
477 */ | |
478 public static Context getContext() { | |
479 return mSingleton; | |
480 } | |
481 | |
482 /** | |
483 * This method is called by SDL using JNI. | |
484 * @return result of getSystemService(name) but executed on UI thread. | |
485 */ | |
486 public Object getSystemServiceFromUiThread(final String name) { | |
487 final Object lock = new Object(); | |
488 final Object[] results = new Object[2]; // array for writable variables | |
489 synchronized (lock) { | |
490 runOnUiThread(new Runnable() { | |
491 @Override | |
492 public void run() { | |
493 synchronized (lock) { | |
494 results[0] = getSystemService(name); | |
495 results[1] = Boolean.TRUE; | |
496 lock.notify(); | |
497 } | |
498 } | |
499 }); | |
500 if (results[1] == null) { | |
501 try { | |
502 lock.wait(); | |
503 } catch (InterruptedException ex) { | |
504 ex.printStackTrace(); | |
505 } | |
506 } | |
507 } | |
508 return results[0]; | |
509 } | |
510 | |
511 static class ShowTextInputTask implements Runnable { | |
512 /* | |
513 * This is used to regulate the pan&scan method to have some offset from | |
514 * the bottom edge of the input region and the top edge of an input | |
515 * method (soft keyboard) | |
516 */ | |
517 static final int HEIGHT_PADDING = 15; | |
518 | |
519 public int x, y, w, h; | |
520 | |
521 public ShowTextInputTask(int x, int y, int w, int h) { | |
522 this.x = x; | |
523 this.y = y; | |
524 this.w = w; | |
525 this.h = h; | |
526 } | |
527 | |
528 @Override | |
529 public void run() { | |
530 AbsoluteLayout.LayoutParams params = new AbsoluteLayout.LayoutParams
( | |
531 w, h + HEIGHT_PADDING, x, y); | |
532 | |
533 if (mTextEdit == null) { | |
534 mTextEdit = new DummyEdit(getContext()); | |
535 | |
536 mLayout.addView(mTextEdit, params); | |
537 } else { | |
538 mTextEdit.setLayoutParams(params); | |
539 } | |
540 | |
541 mTextEdit.setVisibility(View.VISIBLE); | |
542 mTextEdit.requestFocus(); | |
543 | |
544 InputMethodManager imm = (InputMethodManager) getContext().getSystem
Service(Context.INPUT_METHOD_SERVICE); | |
545 imm.showSoftInput(mTextEdit, 0); | |
546 } | |
547 } | |
548 | |
549 /** | |
550 * This method is called by SDL using JNI. | |
551 */ | |
552 public static boolean showTextInput(int x, int y, int w, int h) { | |
553 // Transfer the task to the main thread as a Runnable | |
554 return mSingleton.commandHandler.post(new ShowTextInputTask(x, y, w, h))
; | |
555 } | |
556 | |
557 /** | |
558 * This method is called by SDL using JNI. | |
559 */ | |
560 public static Surface getNativeSurface() { | |
561 return SDLActivity.mSurface.getNativeSurface(); | |
562 } | |
563 | |
564 // Audio | |
565 | |
566 /** | |
567 * This method is called by SDL using JNI. | |
568 */ | |
569 public static int audioInit(int sampleRate, boolean is16Bit, boolean isStere
o, int desiredFrames) { | |
570 int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO
: AudioFormat.CHANNEL_CONFIGURATION_MONO; | |
571 int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat
.ENCODING_PCM_8BIT; | |
572 int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1); | |
573 | |
574 Log.v(TAG, "SDL audio: wanted " + (isStereo ? "stereo" : "mono") + " " +
(is16Bit ? "16-bit" : "8-bit") + " " + (sampleRate / 1000f) + "kHz, " + desired
Frames + " frames buffer"); | |
575 | |
576 // Let the user pick a larger buffer if they really want -- but ye | |
577 // gods they probably shouldn't, the minimums are horrifyingly high | |
578 // latency already | |
579 desiredFrames = Math.max(desiredFrames, (AudioTrack.getMinBufferSize(sam
pleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize); | |
580 | |
581 if (mAudioTrack == null) { | |
582 mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate, | |
583 channelConfig, audioFormat, desiredFrames * frameSize, Audio
Track.MODE_STREAM); | |
584 | |
585 // Instantiating AudioTrack can "succeed" without an exception and t
he track may still be invalid | |
586 // Ref: https://android.googlesource.com/platform/frameworks/base/+/
refs/heads/master/media/java/android/media/AudioTrack.java | |
587 // Ref: http://developer.android.com/reference/android/media/AudioTr
ack.html#getState() | |
588 | |
589 if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) { | |
590 Log.e(TAG, "Failed during initialization of Audio Track"); | |
591 mAudioTrack = null; | |
592 return -1; | |
593 } | |
594 | |
595 mAudioTrack.play(); | |
596 } | |
597 | |
598 Log.v(TAG, "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"); | |
599 | |
600 return 0; | |
601 } | |
602 | |
603 /** | |
604 * This method is called by SDL using JNI. | |
605 */ | |
606 public static void audioWriteShortBuffer(short[] buffer) { | |
607 for (int i = 0; i < buffer.length; ) { | |
608 int result = mAudioTrack.write(buffer, i, buffer.length - i); | |
609 if (result > 0) { | |
610 i += result; | |
611 } else if (result == 0) { | |
612 try { | |
613 Thread.sleep(1); | |
614 } catch(InterruptedException e) { | |
615 // Nom nom | |
616 } | |
617 } else { | |
618 Log.w(TAG, "SDL audio: error return from write(short)"); | |
619 return; | |
620 } | |
621 } | |
622 } | |
623 | |
624 /** | |
625 * This method is called by SDL using JNI. | |
626 */ | |
627 public static void audioWriteByteBuffer(byte[] buffer) { | |
628 for (int i = 0; i < buffer.length; ) { | |
629 int result = mAudioTrack.write(buffer, i, buffer.length - i); | |
630 if (result > 0) { | |
631 i += result; | |
632 } else if (result == 0) { | |
633 try { | |
634 Thread.sleep(1); | |
635 } catch(InterruptedException e) { | |
636 // Nom nom | |
637 } | |
638 } else { | |
639 Log.w(TAG, "SDL audio: error return from write(byte)"); | |
640 return; | |
641 } | |
642 } | |
643 } | |
644 | |
645 /** | |
646 * This method is called by SDL using JNI. | |
647 */ | |
648 public static void audioQuit() { | |
649 if (mAudioTrack != null) { | |
650 mAudioTrack.stop(); | |
651 mAudioTrack = null; | |
652 } | |
653 } | |
654 | |
655 // Input | |
656 | |
657 /** | |
658 * This method is called by SDL using JNI. | |
659 * @return an array which may be empty but is never null. | |
660 */ | |
661 public static int[] inputGetInputDeviceIds(int sources) { | |
662 int[] ids = InputDevice.getDeviceIds(); | |
663 int[] filtered = new int[ids.length]; | |
664 int used = 0; | |
665 for (int i = 0; i < ids.length; ++i) { | |
666 InputDevice device = InputDevice.getDevice(ids[i]); | |
667 if ((device != null) && ((device.getSources() & sources) != 0)) { | |
668 filtered[used++] = device.getId(); | |
669 } | |
670 } | |
671 return Arrays.copyOf(filtered, used); | |
672 } | |
673 | |
674 // Joystick glue code, just a series of stubs that redirect to the SDLJoysti
ckHandler instance | |
675 public static boolean handleJoystickMotionEvent(MotionEvent event) { | |
676 return mJoystickHandler.handleMotionEvent(event); | |
677 } | |
678 | |
679 /** | |
680 * This method is called by SDL using JNI. | |
681 */ | |
682 public static void pollInputDevices() { | |
683 if (SDLActivity.mSDLThread != null) { | |
684 mJoystickHandler.pollInputDevices(); | |
685 } | |
686 } | |
687 | |
688 // APK expansion files support | |
689 | |
690 /** com.android.vending.expansion.zipfile.ZipResourceFile object or null. */ | |
691 private Object expansionFile; | |
692 | |
693 /** com.android.vending.expansion.zipfile.ZipResourceFile's getInputStream()
or null. */ | |
694 private Method expansionFileMethod; | |
695 | |
696 /** | |
697 * This method was called by SDL using JNI. | |
698 * @deprecated because of an incorrect name | |
699 */ | |
700 @Deprecated | |
701 public InputStream openAPKExtensionInputStream(String fileName) throws IOExc
eption { | |
702 return openAPKExpansionInputStream(fileName); | |
703 } | |
704 | |
705 /** | |
706 * This method is called by SDL using JNI. | |
707 * @return an InputStream on success or null if no expansion file was used. | |
708 * @throws IOException on errors. Message is set for the SDL error message. | |
709 */ | |
710 public InputStream openAPKExpansionInputStream(String fileName) throws IOExc
eption { | |
711 // Get a ZipResourceFile representing a merger of both the main and patc
h files | |
712 if (expansionFile == null) { | |
713 String mainHint = nativeGetHint("SDL_ANDROID_APK_EXPANSION_MAIN_FILE
_VERSION"); | |
714 if (mainHint == null) { | |
715 return null; // no expansion use if no main version was set | |
716 } | |
717 String patchHint = nativeGetHint("SDL_ANDROID_APK_EXPANSION_PATCH_FI
LE_VERSION"); | |
718 if (patchHint == null) { | |
719 return null; // no expansion use if no patch version was set | |
720 } | |
721 | |
722 Integer mainVersion; | |
723 Integer patchVersion; | |
724 try { | |
725 mainVersion = Integer.valueOf(mainHint); | |
726 patchVersion = Integer.valueOf(patchHint); | |
727 } catch (NumberFormatException ex) { | |
728 ex.printStackTrace(); | |
729 throw new IOException("No valid file versions set for APK expans
ion files", ex); | |
730 } | |
731 | |
732 try { | |
733 // To avoid direct dependency on Google APK expansion library th
at is | |
734 // not a part of Android SDK we access it using reflection | |
735 expansionFile = Class.forName("com.android.vending.expansion.zip
file.APKExpansionSupport") | |
736 .getMethod("getAPKExpansionZipFile", Context.class, int.clas
s, int.class) | |
737 .invoke(null, this, mainVersion, patchVersion); | |
738 | |
739 expansionFileMethod = expansionFile.getClass() | |
740 .getMethod("getInputStream", String.class); | |
741 } catch (Exception ex) { | |
742 ex.printStackTrace(); | |
743 expansionFile = null; | |
744 expansionFileMethod = null; | |
745 throw new IOException("Could not access APK expansion support li
brary", ex); | |
746 } | |
747 } | |
748 | |
749 // Get an input stream for a known file inside the expansion file ZIPs | |
750 InputStream fileStream; | |
751 try { | |
752 fileStream = (InputStream)expansionFileMethod.invoke(expansionFile,
fileName); | |
753 } catch (Exception ex) { | |
754 // calling "getInputStream" failed | |
755 ex.printStackTrace(); | |
756 throw new IOException("Could not open stream from APK expansion file
", ex); | |
757 } | |
758 | |
759 if (fileStream == null) { | |
760 // calling "getInputStream" was successful but null was returned | |
761 throw new IOException("Could not find path in APK expansion file"); | |
762 } | |
763 | |
764 return fileStream; | |
765 } | |
766 | |
767 // Messagebox | |
768 | |
769 /** Result of current messagebox. Also used for blocking the calling thread.
*/ | |
770 protected final int[] messageboxSelection = new int[1]; | |
771 | |
772 /** Id of current dialog. */ | |
773 protected int dialogs = 0; | |
774 | |
775 /** | |
776 * This method is called by SDL using JNI. | |
777 * Shows the messagebox from UI thread and block calling thread. | |
778 * buttonFlags, buttonIds and buttonTexts must have same length. | |
779 * @param buttonFlags array containing flags for every button. | |
780 * @param buttonIds array containing id for every button. | |
781 * @param buttonTexts array containing text for every button. | |
782 * @param colors null for default or array of length 5 containing colors. | |
783 * @return button id or -1. | |
784 */ | |
785 public int messageboxShowMessageBox( | |
786 final int flags, | |
787 final String title, | |
788 final String message, | |
789 final int[] buttonFlags, | |
790 final int[] buttonIds, | |
791 final String[] buttonTexts, | |
792 final int[] colors) { | |
793 | |
794 messageboxSelection[0] = -1; | |
795 | |
796 // sanity checks | |
797 | |
798 if ((buttonFlags.length != buttonIds.length) && (buttonIds.length != but
tonTexts.length)) { | |
799 return -1; // implementation broken | |
800 } | |
801 | |
802 // collect arguments for Dialog | |
803 | |
804 final Bundle args = new Bundle(); | |
805 args.putInt("flags", flags); | |
806 args.putString("title", title); | |
807 args.putString("message", message); | |
808 args.putIntArray("buttonFlags", buttonFlags); | |
809 args.putIntArray("buttonIds", buttonIds); | |
810 args.putStringArray("buttonTexts", buttonTexts); | |
811 args.putIntArray("colors", colors); | |
812 | |
813 // trigger Dialog creation on UI thread | |
814 | |
815 runOnUiThread(new Runnable() { | |
816 @Override | |
817 public void run() { | |
818 showDialog(dialogs++, args); | |
819 } | |
820 }); | |
821 | |
822 // block the calling thread | |
823 | |
824 synchronized (messageboxSelection) { | |
825 try { | |
826 messageboxSelection.wait(); | |
827 } catch (InterruptedException ex) { | |
828 ex.printStackTrace(); | |
829 return -1; | |
830 } | |
831 } | |
832 | |
833 // return selected value | |
834 | |
835 return messageboxSelection[0]; | |
836 } | |
837 | |
838 @Override | |
839 protected Dialog onCreateDialog(int ignore, Bundle args) { | |
840 | |
841 // TODO set values from "flags" to messagebox dialog | |
842 | |
843 // get colors | |
844 | |
845 int[] colors = args.getIntArray("colors"); | |
846 int backgroundColor; | |
847 int textColor; | |
848 int buttonBorderColor; | |
849 int buttonBackgroundColor; | |
850 int buttonSelectedColor; | |
851 if (colors != null) { | |
852 int i = -1; | |
853 backgroundColor = colors[++i]; | |
854 textColor = colors[++i]; | |
855 buttonBorderColor = colors[++i]; | |
856 buttonBackgroundColor = colors[++i]; | |
857 buttonSelectedColor = colors[++i]; | |
858 } else { | |
859 backgroundColor = Color.TRANSPARENT; | |
860 textColor = Color.TRANSPARENT; | |
861 buttonBorderColor = Color.TRANSPARENT; | |
862 buttonBackgroundColor = Color.TRANSPARENT; | |
863 buttonSelectedColor = Color.TRANSPARENT; | |
864 } | |
865 | |
866 // create dialog with title and a listener to wake up calling thread | |
867 | |
868 final Dialog dialog = new Dialog(this); | |
869 dialog.setTitle(args.getString("title")); | |
870 dialog.setCancelable(false); | |
871 dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { | |
872 @Override | |
873 public void onDismiss(DialogInterface unused) { | |
874 synchronized (messageboxSelection) { | |
875 messageboxSelection.notify(); | |
876 } | |
877 } | |
878 }); | |
879 | |
880 // create text | |
881 | |
882 TextView message = new TextView(this); | |
883 message.setGravity(Gravity.CENTER); | |
884 message.setText(args.getString("message")); | |
885 if (textColor != Color.TRANSPARENT) { | |
886 message.setTextColor(textColor); | |
887 } | |
888 | |
889 // create buttons | |
890 | |
891 int[] buttonFlags = args.getIntArray("buttonFlags"); | |
892 int[] buttonIds = args.getIntArray("buttonIds"); | |
893 String[] buttonTexts = args.getStringArray("buttonTexts"); | |
894 | |
895 final SparseArray<Button> mapping = new SparseArray<Button>(); | |
896 | |
897 LinearLayout buttons = new LinearLayout(this); | |
898 buttons.setOrientation(LinearLayout.HORIZONTAL); | |
899 buttons.setGravity(Gravity.CENTER); | |
900 for (int i = 0; i < buttonTexts.length; ++i) { | |
901 Button button = new Button(this); | |
902 final int id = buttonIds[i]; | |
903 button.setOnClickListener(new View.OnClickListener() { | |
904 @Override | |
905 public void onClick(View v) { | |
906 messageboxSelection[0] = id; | |
907 dialog.dismiss(); | |
908 } | |
909 }); | |
910 if (buttonFlags[i] != 0) { | |
911 // see SDL_messagebox.h | |
912 if ((buttonFlags[i] & 0x00000001) != 0) { | |
913 mapping.put(KeyEvent.KEYCODE_ENTER, button); | |
914 } | |
915 if ((buttonFlags[i] & 0x00000002) != 0) { | |
916 mapping.put(111, button); /* API 11: KeyEvent.KEYCODE_ESCAPE
*/ | |
917 } | |
918 } | |
919 button.setText(buttonTexts[i]); | |
920 if (textColor != Color.TRANSPARENT) { | |
921 button.setTextColor(textColor); | |
922 } | |
923 if (buttonBorderColor != Color.TRANSPARENT) { | |
924 // TODO set color for border of messagebox button | |
925 } | |
926 if (buttonBackgroundColor != Color.TRANSPARENT) { | |
927 Drawable drawable = button.getBackground(); | |
928 if (drawable == null) { | |
929 // setting the color this way removes the style | |
930 button.setBackgroundColor(buttonBackgroundColor); | |
931 } else { | |
932 // setting the color this way keeps the style (gradient, pad
ding, etc.) | |
933 drawable.setColorFilter(buttonBackgroundColor, PorterDuff.Mo
de.MULTIPLY); | |
934 } | |
935 } | |
936 if (buttonSelectedColor != Color.TRANSPARENT) { | |
937 // TODO set color for selected messagebox button | |
938 } | |
939 buttons.addView(button); | |
940 } | |
941 | |
942 // create content | |
943 | |
944 LinearLayout content = new LinearLayout(this); | |
945 content.setOrientation(LinearLayout.VERTICAL); | |
946 content.addView(message); | |
947 content.addView(buttons); | |
948 if (backgroundColor != Color.TRANSPARENT) { | |
949 content.setBackgroundColor(backgroundColor); | |
950 } | |
951 | |
952 // add content to dialog and return | |
953 | |
954 dialog.setContentView(content); | |
955 dialog.setOnKeyListener(new Dialog.OnKeyListener() { | |
956 @Override | |
957 public boolean onKey(DialogInterface d, int keyCode, KeyEvent event)
{ | |
958 Button button = mapping.get(keyCode); | |
959 if (button != null) { | |
960 if (event.getAction() == KeyEvent.ACTION_UP) { | |
961 button.performClick(); | |
962 } | |
963 return true; // also for ignored actions | |
964 } | |
965 return false; | |
966 } | |
967 }); | |
968 | |
969 return dialog; | |
970 } | |
971 } | |
972 | |
973 /** | |
974 Simple nativeInit() runnable | |
975 */ | |
976 class SDLMain implements Runnable { | |
977 @Override | |
978 public void run() { | |
979 // Runs SDL_main() | |
980 SDLActivity.nativeInit(SDLActivity.mSingleton.getArguments()); | |
981 | |
982 Log.v("SDL", "SDL thread terminated"); | |
983 } | |
984 } | |
985 | |
986 | |
987 /** | |
988 SDLSurface. This is what we draw on, so we need to know when it's created | |
989 in order to do anything useful. | |
990 | |
991 Because of this, that's where we set up the SDL thread | |
992 */ | |
993 class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, | |
994 View.OnKeyListener, View.OnTouchListener, SensorEventListener { | |
995 | |
996 // Sensors | |
997 protected static SensorManager mSensorManager; | |
998 protected static Display mDisplay; | |
999 | |
1000 // Keep track of the surface size to normalize touch events | |
1001 protected static float mWidth, mHeight; | |
1002 | |
1003 // Startup | |
1004 public SDLSurface(Context context) { | |
1005 super(context); | |
1006 getHolder().addCallback(this); | |
1007 | |
1008 setFocusable(true); | |
1009 setFocusableInTouchMode(true); | |
1010 requestFocus(); | |
1011 setOnKeyListener(this); | |
1012 setOnTouchListener(this); | |
1013 | |
1014 mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVI
CE)).getDefaultDisplay(); | |
1015 mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_
SERVICE); | |
1016 | |
1017 if(Build.VERSION.SDK_INT >= 12) { | |
1018 setOnGenericMotionListener(new SDLGenericMotionListener_API12()); | |
1019 } | |
1020 | |
1021 // Some arbitrary defaults to avoid a potential division by zero | |
1022 mWidth = 1.0f; | |
1023 mHeight = 1.0f; | |
1024 } | |
1025 | |
1026 public void handlePause() { | |
1027 enableSensor(Sensor.TYPE_ACCELEROMETER, false); | |
1028 } | |
1029 | |
1030 public void handleResume() { | |
1031 setFocusable(true); | |
1032 setFocusableInTouchMode(true); | |
1033 requestFocus(); | |
1034 setOnKeyListener(this); | |
1035 setOnTouchListener(this); | |
1036 enableSensor(Sensor.TYPE_ACCELEROMETER, true); | |
1037 } | |
1038 | |
1039 public Surface getNativeSurface() { | |
1040 return getHolder().getSurface(); | |
1041 } | |
1042 | |
1043 // Called when we have a valid drawing surface | |
1044 @Override | |
1045 public void surfaceCreated(SurfaceHolder holder) { | |
1046 Log.v("SDL", "surfaceCreated()"); | |
1047 holder.setType(SurfaceHolder.SURFACE_TYPE_GPU); | |
1048 } | |
1049 | |
1050 // Called when we lose the surface | |
1051 @Override | |
1052 public void surfaceDestroyed(SurfaceHolder holder) { | |
1053 Log.v("SDL", "surfaceDestroyed()"); | |
1054 // Call this *before* setting mIsSurfaceReady to 'false' | |
1055 SDLActivity.handlePause(); | |
1056 SDLActivity.mIsSurfaceReady = false; | |
1057 SDLActivity.onNativeSurfaceDestroyed(); | |
1058 } | |
1059 | |
1060 // Called when the surface is resized | |
1061 @Override | |
1062 public void surfaceChanged(SurfaceHolder holder, | |
1063 int format, int width, int height) { | |
1064 Log.v("SDL", "surfaceChanged()"); | |
1065 | |
1066 int sdlFormat = 0x15151002; // SDL_PIXELFORMAT_RGB565 by default | |
1067 switch (format) { | |
1068 case PixelFormat.A_8: | |
1069 Log.v("SDL", "pixel format A_8"); | |
1070 break; | |
1071 case PixelFormat.LA_88: | |
1072 Log.v("SDL", "pixel format LA_88"); | |
1073 break; | |
1074 case PixelFormat.L_8: | |
1075 Log.v("SDL", "pixel format L_8"); | |
1076 break; | |
1077 case PixelFormat.RGBA_4444: | |
1078 Log.v("SDL", "pixel format RGBA_4444"); | |
1079 sdlFormat = 0x15421002; // SDL_PIXELFORMAT_RGBA4444 | |
1080 break; | |
1081 case PixelFormat.RGBA_5551: | |
1082 Log.v("SDL", "pixel format RGBA_5551"); | |
1083 sdlFormat = 0x15441002; // SDL_PIXELFORMAT_RGBA5551 | |
1084 break; | |
1085 case PixelFormat.RGBA_8888: | |
1086 Log.v("SDL", "pixel format RGBA_8888"); | |
1087 sdlFormat = 0x16462004; // SDL_PIXELFORMAT_RGBA8888 | |
1088 break; | |
1089 case PixelFormat.RGBX_8888: | |
1090 Log.v("SDL", "pixel format RGBX_8888"); | |
1091 sdlFormat = 0x16261804; // SDL_PIXELFORMAT_RGBX8888 | |
1092 break; | |
1093 case PixelFormat.RGB_332: | |
1094 Log.v("SDL", "pixel format RGB_332"); | |
1095 sdlFormat = 0x14110801; // SDL_PIXELFORMAT_RGB332 | |
1096 break; | |
1097 case PixelFormat.RGB_565: | |
1098 Log.v("SDL", "pixel format RGB_565"); | |
1099 sdlFormat = 0x15151002; // SDL_PIXELFORMAT_RGB565 | |
1100 break; | |
1101 case PixelFormat.RGB_888: | |
1102 Log.v("SDL", "pixel format RGB_888"); | |
1103 // Not sure this is right, maybe SDL_PIXELFORMAT_RGB24 instead? | |
1104 sdlFormat = 0x16161804; // SDL_PIXELFORMAT_RGB888 | |
1105 break; | |
1106 default: | |
1107 Log.v("SDL", "pixel format unknown " + format); | |
1108 break; | |
1109 } | |
1110 | |
1111 mWidth = width; | |
1112 mHeight = height; | |
1113 SDLActivity.onNativeResize(width, height, sdlFormat, mDisplay.getRefresh
Rate()); | |
1114 Log.v("SDL", "Window size: " + width + "x" + height); | |
1115 | |
1116 | |
1117 boolean skip = false; | |
1118 int requestedOrientation = SDLActivity.mSingleton.getRequestedOrientatio
n(); | |
1119 | |
1120 if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) | |
1121 { | |
1122 // Accept any | |
1123 } | |
1124 else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAI
T) | |
1125 { | |
1126 if (mWidth > mHeight) { | |
1127 skip = true; | |
1128 } | |
1129 } else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDS
CAPE) { | |
1130 if (mWidth < mHeight) { | |
1131 skip = true; | |
1132 } | |
1133 } | |
1134 | |
1135 // Special Patch for Square Resolution: Black Berry Passport | |
1136 if (skip) { | |
1137 double min = Math.min(mWidth, mHeight); | |
1138 double max = Math.max(mWidth, mHeight); | |
1139 | |
1140 if (max / min < 1.20) { | |
1141 Log.v("SDL", "Don't skip on such aspect-ratio. Could be a square r
esolution."); | |
1142 skip = false; | |
1143 } | |
1144 } | |
1145 | |
1146 if (skip) { | |
1147 Log.v("SDL", "Skip .. Surface is not ready."); | |
1148 return; | |
1149 } | |
1150 | |
1151 | |
1152 // Set mIsSurfaceReady to 'true' *before* making a call to handleResume | |
1153 SDLActivity.mIsSurfaceReady = true; | |
1154 SDLActivity.onNativeSurfaceChanged(); | |
1155 | |
1156 | |
1157 if (SDLActivity.mSDLThread == null) { | |
1158 // This is the entry point to the C app. | |
1159 // Start up the C app thread and enable sensor input for the first t
ime | |
1160 | |
1161 final Thread sdlThread = new Thread(new SDLMain(), "SDLThread"); | |
1162 enableSensor(Sensor.TYPE_ACCELEROMETER, true); | |
1163 sdlThread.start(); | |
1164 | |
1165 // Set up a listener thread to catch when the native thread ends | |
1166 SDLActivity.mSDLThread = new Thread(new Runnable(){ | |
1167 @Override | |
1168 public void run(){ | |
1169 try { | |
1170 sdlThread.join(); | |
1171 } | |
1172 catch(Exception e){} | |
1173 finally{ | |
1174 // Native thread has finished | |
1175 if (! SDLActivity.mExitCalledFromJava) { | |
1176 SDLActivity.handleNativeExit(); | |
1177 } | |
1178 } | |
1179 } | |
1180 }, "SDLThreadListener"); | |
1181 SDLActivity.mSDLThread.start(); | |
1182 } | |
1183 | |
1184 if (SDLActivity.mHasFocus) { | |
1185 SDLActivity.handleResume(); | |
1186 } | |
1187 } | |
1188 | |
1189 // Key events | |
1190 @Override | |
1191 public boolean onKey(View v, int keyCode, KeyEvent event) { | |
1192 // Dispatch the different events depending on where they come from | |
1193 // Some SOURCE_DPAD or SOURCE_GAMEPAD are also SOURCE_KEYBOARD | |
1194 // So, we try to process them as DPAD or GAMEPAD events first, if that f
ails we try them as KEYBOARD | |
1195 | |
1196 if ( (event.getSource() & InputDevice.SOURCE_GAMEPAD) != 0 || | |
1197 (event.getSource() & InputDevice.SOURCE_DPAD) != 0 ) { | |
1198 if (event.getAction() == KeyEvent.ACTION_DOWN) { | |
1199 if (SDLActivity.onNativePadDown(event.getDeviceId(), keyCode) ==
0) { | |
1200 return true; | |
1201 } | |
1202 } else if (event.getAction() == KeyEvent.ACTION_UP) { | |
1203 if (SDLActivity.onNativePadUp(event.getDeviceId(), keyCode) == 0
) { | |
1204 return true; | |
1205 } | |
1206 } | |
1207 } | |
1208 | |
1209 if( (event.getSource() & InputDevice.SOURCE_KEYBOARD) != 0) { | |
1210 if (event.getAction() == KeyEvent.ACTION_DOWN) { | |
1211 //Log.v("SDL", "key down: " + keyCode); | |
1212 SDLActivity.onNativeKeyDown(keyCode); | |
1213 return true; | |
1214 } | |
1215 else if (event.getAction() == KeyEvent.ACTION_UP) { | |
1216 //Log.v("SDL", "key up: " + keyCode); | |
1217 SDLActivity.onNativeKeyUp(keyCode); | |
1218 return true; | |
1219 } | |
1220 } | |
1221 | |
1222 return false; | |
1223 } | |
1224 | |
1225 // Touch events | |
1226 @Override | |
1227 public boolean onTouch(View v, MotionEvent event) { | |
1228 /* Ref: http://developer.android.com/training/gestures/multi.html */ | |
1229 final int touchDevId = event.getDeviceId(); | |
1230 final int pointerCount = event.getPointerCount(); | |
1231 int action = event.getActionMasked(); | |
1232 int pointerFingerId; | |
1233 int mouseButton; | |
1234 int i = -1; | |
1235 float x,y,p; | |
1236 | |
1237 // !!! FIXME: dump this SDK check after 2.0.4 ships and require API14. | |
1238 if (event.getSource() == InputDevice.SOURCE_MOUSE && SDLActivity.mSepara
teMouseAndTouch) { | |
1239 if (Build.VERSION.SDK_INT < 14) { | |
1240 mouseButton = 1; // For Android==12 all mouse buttons are the
left button | |
1241 } else { | |
1242 try { | |
1243 mouseButton = (Integer) event.getClass().getMethod("getButto
nState").invoke(event); | |
1244 } catch(Exception e) { | |
1245 mouseButton = 1; // oh well. | |
1246 } | |
1247 } | |
1248 SDLActivity.onNativeMouse(mouseButton, action, event.getX(0), event.
getY(0)); | |
1249 } else { | |
1250 switch(action) { | |
1251 case MotionEvent.ACTION_MOVE: | |
1252 for (i = 0; i < pointerCount; i++) { | |
1253 pointerFingerId = event.getPointerId(i); | |
1254 x = event.getX(i) / mWidth; | |
1255 y = event.getY(i) / mHeight; | |
1256 p = event.getPressure(i); | |
1257 if (p > 1.0f) { | |
1258 // may be larger than 1.0f on some devices | |
1259 // see the documentation of getPressure(i) | |
1260 p = 1.0f; | |
1261 } | |
1262 SDLActivity.onNativeTouch(touchDevId, pointerFingerId, a
ction, x, y, p); | |
1263 } | |
1264 break; | |
1265 | |
1266 case MotionEvent.ACTION_UP: | |
1267 case MotionEvent.ACTION_DOWN: | |
1268 // Primary pointer up/down, the index is always zero | |
1269 i = 0; | |
1270 case MotionEvent.ACTION_POINTER_UP: | |
1271 case MotionEvent.ACTION_POINTER_DOWN: | |
1272 // Non primary pointer up/down | |
1273 if (i == -1) { | |
1274 i = event.getActionIndex(); | |
1275 } | |
1276 | |
1277 pointerFingerId = event.getPointerId(i); | |
1278 x = event.getX(i) / mWidth; | |
1279 y = event.getY(i) / mHeight; | |
1280 p = event.getPressure(i); | |
1281 if (p > 1.0f) { | |
1282 // may be larger than 1.0f on some devices | |
1283 // see the documentation of getPressure(i) | |
1284 p = 1.0f; | |
1285 } | |
1286 SDLActivity.onNativeTouch(touchDevId, pointerFingerId, actio
n, x, y, p); | |
1287 break; | |
1288 | |
1289 case MotionEvent.ACTION_CANCEL: | |
1290 for (i = 0; i < pointerCount; i++) { | |
1291 pointerFingerId = event.getPointerId(i); | |
1292 x = event.getX(i) / mWidth; | |
1293 y = event.getY(i) / mHeight; | |
1294 p = event.getPressure(i); | |
1295 if (p > 1.0f) { | |
1296 // may be larger than 1.0f on some devices | |
1297 // see the documentation of getPressure(i) | |
1298 p = 1.0f; | |
1299 } | |
1300 SDLActivity.onNativeTouch(touchDevId, pointerFingerId, M
otionEvent.ACTION_UP, x, y, p); | |
1301 } | |
1302 break; | |
1303 | |
1304 default: | |
1305 break; | |
1306 } | |
1307 } | |
1308 | |
1309 return true; | |
1310 } | |
1311 | |
1312 // Sensor events | |
1313 public void enableSensor(int sensortype, boolean enabled) { | |
1314 // TODO: This uses getDefaultSensor - what if we have >1 accels? | |
1315 if (enabled) { | |
1316 mSensorManager.registerListener(this, | |
1317 mSensorManager.getDefaultSensor(sensortype), | |
1318 SensorManager.SENSOR_DELAY_GAME, null); | |
1319 } else { | |
1320 mSensorManager.unregisterListener(this, | |
1321 mSensorManager.getDefaultSensor(sensortype)); | |
1322 } | |
1323 } | |
1324 | |
1325 @Override | |
1326 public void onAccuracyChanged(Sensor sensor, int accuracy) { | |
1327 // TODO | |
1328 } | |
1329 | |
1330 @Override | |
1331 public void onSensorChanged(SensorEvent event) { | |
1332 if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { | |
1333 float x, y; | |
1334 switch (mDisplay.getRotation()) { | |
1335 case Surface.ROTATION_90: | |
1336 x = -event.values[1]; | |
1337 y = event.values[0]; | |
1338 break; | |
1339 case Surface.ROTATION_270: | |
1340 x = event.values[1]; | |
1341 y = -event.values[0]; | |
1342 break; | |
1343 case Surface.ROTATION_180: | |
1344 x = -event.values[1]; | |
1345 y = -event.values[0]; | |
1346 break; | |
1347 default: | |
1348 x = event.values[0]; | |
1349 y = event.values[1]; | |
1350 break; | |
1351 } | |
1352 SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH, | |
1353 y / SensorManager.GRAVITY_EARTH, | |
1354 event.values[2] / SensorManager.GRAVITY_EA
RTH - 1); | |
1355 } | |
1356 } | |
1357 } | |
1358 | |
1359 /* This is a fake invisible editor view that receives the input and defines the | |
1360 * pan&scan region | |
1361 */ | |
1362 class DummyEdit extends View implements View.OnKeyListener { | |
1363 InputConnection ic; | |
1364 | |
1365 public DummyEdit(Context context) { | |
1366 super(context); | |
1367 setFocusableInTouchMode(true); | |
1368 setFocusable(true); | |
1369 setOnKeyListener(this); | |
1370 } | |
1371 | |
1372 @Override | |
1373 public boolean onCheckIsTextEditor() { | |
1374 return true; | |
1375 } | |
1376 | |
1377 @Override | |
1378 public boolean onKey(View v, int keyCode, KeyEvent event) { | |
1379 | |
1380 // This handles the hardware keyboard input | |
1381 if (event.isPrintingKey()) { | |
1382 if (event.getAction() == KeyEvent.ACTION_DOWN) { | |
1383 ic.commitText(String.valueOf((char) event.getUnicodeChar()), 1); | |
1384 } | |
1385 return true; | |
1386 } | |
1387 | |
1388 if (event.getAction() == KeyEvent.ACTION_DOWN) { | |
1389 SDLActivity.onNativeKeyDown(keyCode); | |
1390 return true; | |
1391 } else if (event.getAction() == KeyEvent.ACTION_UP) { | |
1392 SDLActivity.onNativeKeyUp(keyCode); | |
1393 return true; | |
1394 } | |
1395 | |
1396 return false; | |
1397 } | |
1398 | |
1399 // | |
1400 @Override | |
1401 public boolean onKeyPreIme (int keyCode, KeyEvent event) { | |
1402 // As seen on StackOverflow: http://stackoverflow.com/questions/7634346/
keyboard-hide-event | |
1403 // FIXME: Discussion at http://bugzilla.libsdl.org/show_bug.cgi?id=1639 | |
1404 // FIXME: This is not a 100% effective solution to the problem of detect
ing if the keyboard is showing or not | |
1405 // FIXME: A more effective solution would be to change our Layout from A
bsoluteLayout to Relative or Linear | |
1406 // FIXME: And determine the keyboard presence doing this: http://stackov
erflow.com/questions/2150078/how-to-check-visibility-of-software-keyboard-in-and
roid | |
1407 // FIXME: An even more effective way would be if Android provided this o
ut of the box, but where would the fun be in that :) | |
1408 if (event.getAction()==KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE
_BACK) { | |
1409 if (SDLActivity.mTextEdit != null && SDLActivity.mTextEdit.getVisibi
lity() == View.VISIBLE) { | |
1410 SDLActivity.onNativeKeyboardFocusLost(); | |
1411 } | |
1412 } | |
1413 return super.onKeyPreIme(keyCode, event); | |
1414 } | |
1415 | |
1416 @Override | |
1417 public InputConnection onCreateInputConnection(EditorInfo outAttrs) { | |
1418 ic = new SDLInputConnection(this, true); | |
1419 | |
1420 outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VAR
IATION_VISIBLE_PASSWORD; | |
1421 outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI | |
1422 | 33554432 /* API 11: EditorInfo.IME_FLAG_NO_FULLSCREEN */; | |
1423 | |
1424 return ic; | |
1425 } | |
1426 } | |
1427 | |
1428 class SDLInputConnection extends BaseInputConnection { | |
1429 | |
1430 public SDLInputConnection(View targetView, boolean fullEditor) { | |
1431 super(targetView, fullEditor); | |
1432 | |
1433 } | |
1434 | |
1435 @Override | |
1436 public boolean sendKeyEvent(KeyEvent event) { | |
1437 | |
1438 /* | |
1439 * This handles the keycodes from soft keyboard (and IME-translated | |
1440 * input from hardkeyboard) | |
1441 */ | |
1442 int keyCode = event.getKeyCode(); | |
1443 if (event.getAction() == KeyEvent.ACTION_DOWN) { | |
1444 if (event.isPrintingKey()) { | |
1445 commitText(String.valueOf((char) event.getUnicodeChar()), 1); | |
1446 } | |
1447 SDLActivity.onNativeKeyDown(keyCode); | |
1448 return true; | |
1449 } else if (event.getAction() == KeyEvent.ACTION_UP) { | |
1450 | |
1451 SDLActivity.onNativeKeyUp(keyCode); | |
1452 return true; | |
1453 } | |
1454 return super.sendKeyEvent(event); | |
1455 } | |
1456 | |
1457 @Override | |
1458 public boolean commitText(CharSequence text, int newCursorPosition) { | |
1459 | |
1460 nativeCommitText(text.toString(), newCursorPosition); | |
1461 | |
1462 return super.commitText(text, newCursorPosition); | |
1463 } | |
1464 | |
1465 @Override | |
1466 public boolean setComposingText(CharSequence text, int newCursorPosition) { | |
1467 | |
1468 nativeSetComposingText(text.toString(), newCursorPosition); | |
1469 | |
1470 return super.setComposingText(text, newCursorPosition); | |
1471 } | |
1472 | |
1473 public native void nativeCommitText(String text, int newCursorPosition); | |
1474 | |
1475 public native void nativeSetComposingText(String text, int newCursorPosition
); | |
1476 | |
1477 @Override | |
1478 public boolean deleteSurroundingText(int beforeLength, int afterLength) { | |
1479 // Workaround to capture backspace key. Ref: http://stackoverflow.com/qu
estions/14560344/android-backspace-in-webview-baseinputconnection | |
1480 if (beforeLength == 1 && afterLength == 0) { | |
1481 // backspace | |
1482 return super.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEven
t.KEYCODE_DEL)) | |
1483 && super.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.
KEYCODE_DEL)); | |
1484 } | |
1485 | |
1486 return super.deleteSurroundingText(beforeLength, afterLength); | |
1487 } | |
1488 } | |
1489 | |
1490 /* A null joystick handler for API level < 12 devices (the accelerometer is hand
led separately) */ | |
1491 class SDLJoystickHandler { | |
1492 | |
1493 /** | |
1494 * Handles given MotionEvent. | |
1495 * @param event the event to be handled. | |
1496 * @return if given event was processed. | |
1497 */ | |
1498 public boolean handleMotionEvent(MotionEvent event) { | |
1499 return false; | |
1500 } | |
1501 | |
1502 /** | |
1503 * Handles adding and removing of input devices. | |
1504 */ | |
1505 public void pollInputDevices() { | |
1506 } | |
1507 } | |
1508 | |
1509 /* Actual joystick functionality available for API >= 12 devices */ | |
1510 class SDLJoystickHandler_API12 extends SDLJoystickHandler { | |
1511 | |
1512 static class SDLJoystick { | |
1513 public int device_id; | |
1514 public String name; | |
1515 public ArrayList<InputDevice.MotionRange> axes; | |
1516 public ArrayList<InputDevice.MotionRange> hats; | |
1517 } | |
1518 static class RangeComparator implements Comparator<InputDevice.MotionRange>
{ | |
1519 @Override | |
1520 public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange
arg1) { | |
1521 return arg0.getAxis() - arg1.getAxis(); | |
1522 } | |
1523 } | |
1524 | |
1525 private ArrayList<SDLJoystick> mJoysticks; | |
1526 | |
1527 public SDLJoystickHandler_API12() { | |
1528 | |
1529 mJoysticks = new ArrayList<SDLJoystick>(); | |
1530 } | |
1531 | |
1532 @Override | |
1533 public void pollInputDevices() { | |
1534 int[] deviceIds = InputDevice.getDeviceIds(); | |
1535 // It helps processing the device ids in reverse order | |
1536 // For example, in the case of the XBox 360 wireless dongle, | |
1537 // so the first controller seen by SDL matches what the receiver | |
1538 // considers to be the first controller | |
1539 | |
1540 for(int i=deviceIds.length-1; i>-1; i--) { | |
1541 SDLJoystick joystick = getJoystick(deviceIds[i]); | |
1542 if (joystick == null) { | |
1543 joystick = new SDLJoystick(); | |
1544 InputDevice joystickDevice = InputDevice.getDevice(deviceIds[i])
; | |
1545 | |
1546 if ( | |
1547 (joystickDevice.getSources() & InputDevice.SOURCE_CLASS_JO
YSTICK) != 0 | |
1548 || | |
1549 (joystickDevice.getSources() & InputDevice.SOURCE_CLASS_BU
TTON) != 0 | |
1550 ) | |
1551 { | |
1552 joystick.device_id = deviceIds[i]; | |
1553 joystick.name = joystickDevice.getName(); | |
1554 joystick.axes = new ArrayList<InputDevice.MotionRange>(); | |
1555 joystick.hats = new ArrayList<InputDevice.MotionRange>(); | |
1556 | |
1557 List<InputDevice.MotionRange> ranges = joystickDevice.getMot
ionRanges(); | |
1558 Collections.sort(ranges, new RangeComparator()); | |
1559 for (InputDevice.MotionRange range : ranges ) { | |
1560 if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTI
CK) != 0 ) { | |
1561 if (range.getAxis() == MotionEvent.AXIS_HAT_X || | |
1562 range.getAxis() == MotionEvent.AXIS_HAT_Y) { | |
1563 joystick.hats.add(range); | |
1564 } | |
1565 else { | |
1566 joystick.axes.add(range); | |
1567 } | |
1568 } | |
1569 } | |
1570 | |
1571 mJoysticks.add(joystick); | |
1572 SDLActivity.nativeAddJoystick(joystick.device_id, joystick.n
ame, 0, -1, | |
1573 joystick.axes.size(), joystick
.hats.size()/2, 0); | |
1574 } | |
1575 } | |
1576 } | |
1577 | |
1578 /* Check removed devices */ | |
1579 ArrayList<Integer> removedDevices = new ArrayList<Integer>(); | |
1580 for(int i=0; i < mJoysticks.size(); i++) { | |
1581 int device_id = mJoysticks.get(i).device_id; | |
1582 int j; | |
1583 for (j=0; j < deviceIds.length; j++) { | |
1584 if (device_id == deviceIds[j]) break; | |
1585 } | |
1586 if (j == deviceIds.length) { | |
1587 removedDevices.add(Integer.valueOf(device_id)); | |
1588 } | |
1589 } | |
1590 | |
1591 for(int i=0; i < removedDevices.size(); i++) { | |
1592 int device_id = removedDevices.get(i).intValue(); | |
1593 SDLActivity.nativeRemoveJoystick(device_id); | |
1594 for (int j=0; j < mJoysticks.size(); j++) { | |
1595 if (mJoysticks.get(j).device_id == device_id) { | |
1596 mJoysticks.remove(j); | |
1597 break; | |
1598 } | |
1599 } | |
1600 } | |
1601 } | |
1602 | |
1603 protected SDLJoystick getJoystick(int device_id) { | |
1604 for(int i=0; i < mJoysticks.size(); i++) { | |
1605 if (mJoysticks.get(i).device_id == device_id) { | |
1606 return mJoysticks.get(i); | |
1607 } | |
1608 } | |
1609 return null; | |
1610 } | |
1611 | |
1612 @Override | |
1613 public boolean handleMotionEvent(MotionEvent event) { | |
1614 if ( (event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0) { | |
1615 int actionPointerIndex = event.getActionIndex(); | |
1616 int action = event.getActionMasked(); | |
1617 switch(action) { | |
1618 case MotionEvent.ACTION_MOVE: | |
1619 SDLJoystick joystick = getJoystick(event.getDeviceId()); | |
1620 if ( joystick != null ) { | |
1621 for (int i = 0; i < joystick.axes.size(); i++) { | |
1622 InputDevice.MotionRange range = joystick.axes.get(i)
; | |
1623 /* Normalize the value to -1...1 */ | |
1624 float value = ( event.getAxisValue( range.getAxis(),
actionPointerIndex) - range.getMin() ) / range.getRange() * 2.0f - 1.0f; | |
1625 SDLActivity.onNativeJoy(joystick.device_id, i, value
); | |
1626 } | |
1627 for (int i = 0; i < joystick.hats.size(); i+=2) { | |
1628 int hatX = Math.round(event.getAxisValue( joystick.h
ats.get(i).getAxis(), actionPointerIndex ) ); | |
1629 int hatY = Math.round(event.getAxisValue( joystick.h
ats.get(i+1).getAxis(), actionPointerIndex ) ); | |
1630 SDLActivity.onNativeHat(joystick.device_id, i/2, hat
X, hatY ); | |
1631 } | |
1632 } | |
1633 break; | |
1634 default: | |
1635 break; | |
1636 } | |
1637 } | |
1638 return true; | |
1639 } | |
1640 } | |
1641 | |
1642 class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener { | |
1643 // Generic Motion (mouse hover, joystick...) events go here | |
1644 @Override | |
1645 public boolean onGenericMotion(View v, MotionEvent event) { | |
1646 float x, y; | |
1647 int action; | |
1648 | |
1649 switch ( event.getSource() ) { | |
1650 case InputDevice.SOURCE_JOYSTICK: | |
1651 case InputDevice.SOURCE_GAMEPAD: | |
1652 case InputDevice.SOURCE_DPAD: | |
1653 SDLActivity.handleJoystickMotionEvent(event); | |
1654 return true; | |
1655 | |
1656 case InputDevice.SOURCE_MOUSE: | |
1657 action = event.getActionMasked(); | |
1658 switch (action) { | |
1659 case MotionEvent.ACTION_SCROLL: | |
1660 x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0); | |
1661 y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0); | |
1662 SDLActivity.onNativeMouse(0, action, x, y); | |
1663 return true; | |
1664 | |
1665 case MotionEvent.ACTION_HOVER_MOVE: | |
1666 x = event.getX(0); | |
1667 y = event.getY(0); | |
1668 | |
1669 SDLActivity.onNativeMouse(0, action, x, y); | |
1670 return true; | |
1671 | |
1672 default: | |
1673 break; | |
1674 } | |
1675 break; | |
1676 | |
1677 default: | |
1678 break; | |
1679 } | |
1680 | |
1681 // Event was not managed | |
1682 return false; | |
1683 } | |
1684 } | |
OLD | NEW |