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