Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(236)

Side by Side Diff: platform_tools/android/apps/visualbenchsdl/src/main/java/org/libsdl/app/SDLActivity.java

Issue 1415453009: Wire up SDL on Android (Closed) Base URL: https://skia.googlesource.com/skia.git@sdl
Patch Set: fix appurify Created 5 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698