SDL  2.0
SDLActivity.java
Go to the documentation of this file.
1 package org.libsdl.app;
2 
3 import java.io.IOException;
4 import java.io.InputStream;
5 import java.util.Arrays;
6 import java.util.Hashtable;
7 import java.lang.reflect.Method;
8 import java.lang.Math;
9 
10 import android.app.*;
11 import android.content.*;
12 import android.content.res.Configuration;
13 import android.text.InputType;
14 import android.view.*;
15 import android.view.inputmethod.BaseInputConnection;
16 import android.view.inputmethod.EditorInfo;
17 import android.view.inputmethod.InputConnection;
18 import android.view.inputmethod.InputMethodManager;
19 import android.widget.RelativeLayout;
20 import android.widget.Button;
21 import android.widget.LinearLayout;
22 import android.widget.TextView;
23 import android.os.*;
24 import android.util.DisplayMetrics;
25 import android.util.Log;
26 import android.util.SparseArray;
27 import android.graphics.*;
28 import android.graphics.drawable.Drawable;
29 import android.hardware.*;
30 import android.content.pm.ActivityInfo;
31 import android.content.pm.PackageManager;
32 import android.content.pm.ApplicationInfo;
33 
34 /**
35  SDL Activity
36 */
37 public class SDLActivity extends Activity implements View.OnSystemUiVisibilityChangeListener {
38  private static final String TAG = "SDL";
39 
40  public static boolean mIsResumedCalled, mIsSurfaceReady, mHasFocus;
41 
42  // Cursor types
43  private static final int SDL_SYSTEM_CURSOR_NONE = -1;
44  private static final int SDL_SYSTEM_CURSOR_ARROW = 0;
45  private static final int SDL_SYSTEM_CURSOR_IBEAM = 1;
46  private static final int SDL_SYSTEM_CURSOR_WAIT = 2;
47  private static final int SDL_SYSTEM_CURSOR_CROSSHAIR = 3;
48  private static final int SDL_SYSTEM_CURSOR_WAITARROW = 4;
49  private static final int SDL_SYSTEM_CURSOR_SIZENWSE = 5;
50  private static final int SDL_SYSTEM_CURSOR_SIZENESW = 6;
51  private static final int SDL_SYSTEM_CURSOR_SIZEWE = 7;
52  private static final int SDL_SYSTEM_CURSOR_SIZENS = 8;
53  private static final int SDL_SYSTEM_CURSOR_SIZEALL = 9;
54  private static final int SDL_SYSTEM_CURSOR_NO = 10;
55  private static final int SDL_SYSTEM_CURSOR_HAND = 11;
56 
57  protected static final int SDL_ORIENTATION_UNKNOWN = 0;
58  protected static final int SDL_ORIENTATION_LANDSCAPE = 1;
59  protected static final int SDL_ORIENTATION_LANDSCAPE_FLIPPED = 2;
60  protected static final int SDL_ORIENTATION_PORTRAIT = 3;
61  protected static final int SDL_ORIENTATION_PORTRAIT_FLIPPED = 4;
62 
63  protected static int mCurrentOrientation;
64 
65  // Handle the state of the native layer
66  public enum NativeState {
67  INIT, RESUMED, PAUSED
68  }
69 
72 
73  public static boolean mExitCalledFromJava;
74 
75  /** If shared libraries (e.g. SDL or the native application) could not be loaded. */
76  public static boolean mBrokenLibraries;
77 
78  // If we want to separate mouse and touch events.
79  // This is only toggled in native code when a hint is set!
80  public static boolean mSeparateMouseAndTouch;
81 
82  // Main components
83  protected static SDLActivity mSingleton;
84  protected static SDLSurface mSurface;
85  protected static View mTextEdit;
86  protected static boolean mScreenKeyboardShown;
87  protected static ViewGroup mLayout;
88  protected static SDLClipboardHandler mClipboardHandler;
89  protected static Hashtable<Integer, Object> mCursors;
90  protected static int mLastCursorID;
91  protected static SDLGenericMotionListener_API12 mMotionListener;
93 
94  // This is what SDL runs in. It invokes SDL_main(), eventually
95  protected static Thread mSDLThread;
96 
97  protected static SDLGenericMotionListener_API12 getMotionListener() {
98  if (mMotionListener == null) {
99  if (Build.VERSION.SDK_INT >= 26) {
100  mMotionListener = new SDLGenericMotionListener_API26();
101  } else
102  if (Build.VERSION.SDK_INT >= 24) {
103  mMotionListener = new SDLGenericMotionListener_API24();
104  } else {
105  mMotionListener = new SDLGenericMotionListener_API12();
106  }
107  }
108 
109  return mMotionListener;
110  }
111 
112  /**
113  * This method returns the name of the shared object with the application entry point
114  * It can be overridden by derived classes.
115  */
116  protected String getMainSharedObject() {
117  String library;
118  String[] libraries = SDLActivity.mSingleton.getLibraries();
119  if (libraries.length > 0) {
120  library = "lib" + libraries[libraries.length - 1] + ".so";
121  } else {
122  library = "libmain.so";
123  }
124  return getContext().getApplicationInfo().nativeLibraryDir + "/" + library;
125  }
126 
127  /**
128  * This method returns the name of the application entry point
129  * It can be overridden by derived classes.
130  */
131  protected String getMainFunction() {
132  return "SDL_main";
133  }
134 
135  /**
136  * This method is called by SDL before loading the native shared libraries.
137  * It can be overridden to provide names of shared libraries to be loaded.
138  * The default implementation returns the defaults. It never returns null.
139  * An array returned by a new implementation must at least contain "SDL2".
140  * Also keep in mind that the order the libraries are loaded may matter.
141  * @return names of shared libraries to be loaded (e.g. "SDL2", "main").
142  */
143  protected String[] getLibraries() {
144  return new String[] {
145  "SDL2",
146  // "SDL2_image",
147  // "SDL2_mixer",
148  // "SDL2_net",
149  // "SDL2_ttf",
150  "main"
151  };
152  }
153 
154  // Load the .so
155  public void loadLibraries() {
156  for (String lib : getLibraries()) {
157  SDL.loadLibrary(lib);
158  }
159  }
160 
161  /**
162  * This method is called by SDL before starting the native application thread.
163  * It can be overridden to provide the arguments after the application name.
164  * The default implementation returns an empty array. It never returns null.
165  * @return arguments for the native application.
166  */
167  protected String[] getArguments() {
168  return new String[0];
169  }
170 
171  public static void initialize() {
172  // The static nature of the singleton and Android quirkyness force us to initialize everything here
173  // Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values
174  mSingleton = null;
175  mSurface = null;
176  mTextEdit = null;
177  mLayout = null;
178  mClipboardHandler = null;
179  mCursors = new Hashtable<Integer, Object>();
180  mLastCursorID = 0;
181  mSDLThread = null;
182  mExitCalledFromJava = false;
183  mBrokenLibraries = false;
184  mIsResumedCalled = false;
185  mIsSurfaceReady = false;
186  mHasFocus = true;
187  mNextNativeState = NativeState.INIT;
188  mCurrentNativeState = NativeState.INIT;
189  }
190 
191  // Setup
192  @Override
193  protected void onCreate(Bundle savedInstanceState) {
194  Log.v(TAG, "Device: " + Build.DEVICE);
195  Log.v(TAG, "Model: " + Build.MODEL);
196  Log.v(TAG, "onCreate()");
197  super.onCreate(savedInstanceState);
198 
199  // Load shared libraries
200  String errorMsgBrokenLib = "";
201  try {
202  loadLibraries();
203  } catch(UnsatisfiedLinkError e) {
204  System.err.println(e.getMessage());
205  mBrokenLibraries = true;
206  errorMsgBrokenLib = e.getMessage();
207  } catch(Exception e) {
208  System.err.println(e.getMessage());
209  mBrokenLibraries = true;
210  errorMsgBrokenLib = e.getMessage();
211  }
212 
213  if (mBrokenLibraries)
214  {
215  mSingleton = this;
216  AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this);
217  dlgAlert.setMessage("An error occurred while trying to start the application. Please try again and/or reinstall."
218  + System.getProperty("line.separator")
219  + System.getProperty("line.separator")
220  + "Error: " + errorMsgBrokenLib);
221  dlgAlert.setTitle("SDL Error");
222  dlgAlert.setPositiveButton("Exit",
223  new DialogInterface.OnClickListener() {
224  @Override
225  public void onClick(DialogInterface dialog,int id) {
226  // if this button is clicked, close current activity
227  SDLActivity.mSingleton.finish();
228  }
229  });
230  dlgAlert.setCancelable(false);
231  dlgAlert.create().show();
232 
233  return;
234  }
235 
236  // Set up JNI
237  SDL.setupJNI();
238 
239  // Initialize state
240  SDL.initialize();
241 
242  // So we can call stuff from static callbacks
243  mSingleton = this;
244  SDL.setContext(this);
245 
246  if (Build.VERSION.SDK_INT >= 11) {
247  mClipboardHandler = new SDLClipboardHandler_API11();
248  } else {
249  /* Before API 11, no clipboard notification (eg no SDL_CLIPBOARDUPDATE) */
250  mClipboardHandler = new SDLClipboardHandler_Old();
251  }
252 
253  mHIDDeviceManager = HIDDeviceManager.acquire(this);
254 
255  // Set up the surface
256  mSurface = new SDLSurface(getApplication());
257 
258  mLayout = new RelativeLayout(this);
259  mLayout.addView(mSurface);
260 
261  // Get our current screen orientation and pass it down.
262  mCurrentOrientation = SDLActivity.getCurrentOrientation();
263  SDLActivity.onNativeOrientationChanged(mCurrentOrientation);
264 
265  setContentView(mLayout);
266 
267  setWindowStyle(false);
268 
269  getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(this);
270 
271  // Get filename from "Open with" of another application
272  Intent intent = getIntent();
273  if (intent != null && intent.getData() != null) {
274  String filename = intent.getData().getPath();
275  if (filename != null) {
276  Log.v(TAG, "Got filename: " + filename);
277  SDLActivity.onNativeDropFile(filename);
278  }
279  }
280  }
281 
282  // Events
283  @Override
284  protected void onPause() {
285  Log.v(TAG, "onPause()");
286  super.onPause();
287  mNextNativeState = NativeState.PAUSED;
288  mIsResumedCalled = false;
289 
291  return;
292  }
293 
294  if (mHIDDeviceManager != null) {
295  mHIDDeviceManager.setFrozen(true);
296  }
297 
299  }
300 
301  @Override
302  protected void onResume() {
303  Log.v(TAG, "onResume()");
304  super.onResume();
305  mNextNativeState = NativeState.RESUMED;
306  mIsResumedCalled = true;
307 
309  return;
310  }
311 
312  if (mHIDDeviceManager != null) {
313  mHIDDeviceManager.setFrozen(false);
314  }
315 
317  }
318 
319  public static int getCurrentOrientation() {
320  final Context context = SDLActivity.getContext();
321  final Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
322 
324 
325  switch (display.getRotation()) {
326  case Surface.ROTATION_0:
327  result = SDL_ORIENTATION_PORTRAIT;
328  break;
329 
330  case Surface.ROTATION_90:
331  result = SDL_ORIENTATION_LANDSCAPE;
332  break;
333 
334  case Surface.ROTATION_180:
336  break;
337 
338  case Surface.ROTATION_270:
340  break;
341  }
342 
343  return result;
344  }
345 
346  @Override
347  public void onWindowFocusChanged(boolean hasFocus) {
348  super.onWindowFocusChanged(hasFocus);
349  Log.v(TAG, "onWindowFocusChanged(): " + hasFocus);
350 
352  return;
353  }
354 
355  SDLActivity.mHasFocus = hasFocus;
356  if (hasFocus) {
357  mNextNativeState = NativeState.RESUMED;
358  SDLActivity.getMotionListener().reclaimRelativeMouseModeIfNeeded();
359  } else {
360  mNextNativeState = NativeState.PAUSED;
361  }
362 
364  }
365 
366  @Override
367  public void onLowMemory() {
368  Log.v(TAG, "onLowMemory()");
369  super.onLowMemory();
370 
372  return;
373  }
374 
376  }
377 
378  @Override
379  protected void onDestroy() {
380  Log.v(TAG, "onDestroy()");
381 
382  if (mHIDDeviceManager != null) {
383  HIDDeviceManager.release(mHIDDeviceManager);
384  mHIDDeviceManager = null;
385  }
386 
388  super.onDestroy();
389  // Reset everything in case the user re opens the app
391  return;
392  }
393 
394  mNextNativeState = NativeState.PAUSED;
396 
397  // Send a quit message to the application
400 
401  // Now wait for the SDL thread to quit
402  if (SDLActivity.mSDLThread != null) {
403  try {
404  SDLActivity.mSDLThread.join();
405  } catch(Exception e) {
406  Log.v(TAG, "Problem stopping thread: " + e);
407  }
408  SDLActivity.mSDLThread = null;
409 
410  //Log.v(TAG, "Finished waiting for SDL thread");
411  }
412 
413  super.onDestroy();
414 
415  // Reset everything in case the user re opens the app
417  }
418 
419  @Override
420  public void onBackPressed() {
421  // Check if we want to block the back button in case of mouse right click.
422  //
423  // If we do, the normal hardware back button will no longer work and people have to use home,
424  // but the mouse right click will work.
425  //
426  String trapBack = SDLActivity.nativeGetHint("SDL_ANDROID_TRAP_BACK_BUTTON");
427  if ((trapBack != null) && trapBack.equals("1")) {
428  // Exit and let the mouse handler handle this button (if appropriate)
429  return;
430  }
431 
432  // Default system back button behavior.
433  super.onBackPressed();
434  }
435 
436  // Called by JNI from SDL.
437  public static void manualBackButton() {
438  mSingleton.pressBackButton();
439  }
440 
441  // Used to get us onto the activity's main thread
442  public void pressBackButton() {
443  runOnUiThread(new Runnable() {
444  @Override
445  public void run() {
447  }
448  });
449  }
450 
451  // Used to access the system back behavior.
452  public void superOnBackPressed() {
453  super.onBackPressed();
454  }
455 
456  @Override
457  public boolean dispatchKeyEvent(KeyEvent event) {
458 
460  return false;
461  }
462 
463  int keyCode = event.getKeyCode();
464  // Ignore certain special keys so they're handled by Android
465  if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ||
466  keyCode == KeyEvent.KEYCODE_VOLUME_UP ||
467  keyCode == KeyEvent.KEYCODE_CAMERA ||
468  keyCode == KeyEvent.KEYCODE_ZOOM_IN || /* API 11 */
469  keyCode == KeyEvent.KEYCODE_ZOOM_OUT /* API 11 */
470  ) {
471  return false;
472  }
473  return super.dispatchKeyEvent(event);
474  }
475 
476  /* Transition to next state */
477  public static void handleNativeState() {
478 
479  if (mNextNativeState == mCurrentNativeState) {
480  // Already in same state, discard.
481  return;
482  }
483 
484  // Try a transition to init state
485  if (mNextNativeState == NativeState.INIT) {
486 
487  mCurrentNativeState = mNextNativeState;
488  return;
489  }
490 
491  // Try a transition to paused state
492  if (mNextNativeState == NativeState.PAUSED) {
493  nativePause();
494  if (mSurface != null)
495  mSurface.handlePause();
496  mCurrentNativeState = mNextNativeState;
497  return;
498  }
499 
500  // Try a transition to resumed state
501  if (mNextNativeState == NativeState.RESUMED) {
502  if (mIsSurfaceReady && mHasFocus && mIsResumedCalled) {
503  if (mSDLThread == null) {
504  // This is the entry point to the C app.
505  // Start up the C app thread and enable sensor input for the first time
506  // FIXME: Why aren't we enabling sensor input at start?
507 
508  mSDLThread = new Thread(new SDLMain(), "SDLThread");
509  mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, true);
510  mSDLThread.start();
511  }
512 
513  nativeResume();
514  mSurface.handleResume();
515  mCurrentNativeState = mNextNativeState;
516  }
517  }
518  }
519 
520  /* The native thread has finished */
521  public static void handleNativeExit() {
522  SDLActivity.mSDLThread = null;
523  if (mSingleton != null) {
524  mSingleton.finish();
525  }
526  }
527 
528 
529  // Messages from the SDLMain thread
530  static final int COMMAND_CHANGE_TITLE = 1;
531  static final int COMMAND_CHANGE_WINDOW_STYLE = 2;
532  static final int COMMAND_TEXTEDIT_HIDE = 3;
533  static final int COMMAND_SET_KEEP_SCREEN_ON = 5;
534 
535  protected static final int COMMAND_USER = 0x8000;
536 
537  protected static boolean mFullscreenModeActive;
538 
539  /**
540  * This method is called by SDL if SDL did not handle a message itself.
541  * This happens if a received message contains an unsupported command.
542  * Method can be overwritten to handle Messages in a different class.
543  * @param command the command of the message.
544  * @param param the parameter of the message. May be null.
545  * @return if the message was handled in overridden method.
546  */
547  protected boolean onUnhandledMessage(int command, Object param) {
548  return false;
549  }
550 
551  /**
552  * A Handler class for Messages from native SDL applications.
553  * It uses current Activities as target (e.g. for the title).
554  * static to prevent implicit references to enclosing object.
555  */
556  protected static class SDLCommandHandler extends Handler {
557  @Override
558  public void handleMessage(Message msg) {
559  Context context = SDL.getContext();
560  if (context == null) {
561  Log.e(TAG, "error handling message, getContext() returned null");
562  return;
563  }
564  switch (msg.arg1) {
565  case COMMAND_CHANGE_TITLE:
566  if (context instanceof Activity) {
567  ((Activity) context).setTitle((String)msg.obj);
568  } else {
569  Log.e(TAG, "error handling message, getContext() returned no Activity");
570  }
571  break;
572  case COMMAND_CHANGE_WINDOW_STYLE:
573  if (Build.VERSION.SDK_INT < 19) {
574  // This version of Android doesn't support the immersive fullscreen mode
575  break;
576  }
577  if (context instanceof Activity) {
578  Window window = ((Activity) context).getWindow();
579  if (window != null) {
580  if ((msg.obj instanceof Integer) && (((Integer) msg.obj).intValue() != 0)) {
581  int flags = View.SYSTEM_UI_FLAG_FULLSCREEN |
582  View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
583  View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY |
584  View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
585  View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
586  View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.INVISIBLE;
587  window.getDecorView().setSystemUiVisibility(flags);
588  window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
589  window.clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
591  } else {
592  int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_VISIBLE;
593  window.getDecorView().setSystemUiVisibility(flags);
594  window.addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
595  window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
597  }
598  }
599  } else {
600  Log.e(TAG, "error handling message, getContext() returned no Activity");
601  }
602  break;
603  case COMMAND_TEXTEDIT_HIDE:
604  if (mTextEdit != null) {
605  // Note: On some devices setting view to GONE creates a flicker in landscape.
606  // Setting the View's sizes to 0 is similar to GONE but without the flicker.
607  // The sizes will be set to useful values when the keyboard is shown again.
608  mTextEdit.setLayoutParams(new RelativeLayout.LayoutParams(0, 0));
609 
610  InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
611  imm.hideSoftInputFromWindow(mTextEdit.getWindowToken(), 0);
612 
613  mScreenKeyboardShown = false;
614  }
615  break;
616  case COMMAND_SET_KEEP_SCREEN_ON:
617  {
618  if (context instanceof Activity) {
619  Window window = ((Activity) context).getWindow();
620  if (window != null) {
621  if ((msg.obj instanceof Integer) && (((Integer) msg.obj).intValue() != 0)) {
622  window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
623  } else {
624  window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
625  }
626  }
627  }
628  break;
629  }
630  default:
631  if ((context instanceof SDLActivity) && !((SDLActivity) context).onUnhandledMessage(msg.arg1, msg.obj)) {
632  Log.e(TAG, "error handling message, command is " + msg.arg1);
633  }
634  }
635  }
636  }
637 
638  // Handler for the messages
639  Handler commandHandler = new SDLCommandHandler();
640 
641  // Send a message from the SDLMain thread
642  boolean sendCommand(int command, Object data) {
643  Message msg = commandHandler.obtainMessage();
644  msg.arg1 = command;
645  msg.obj = data;
646  boolean result = commandHandler.sendMessage(msg);
647 
648  if ((Build.VERSION.SDK_INT >= 19) && (command == COMMAND_CHANGE_WINDOW_STYLE)) {
649  // Ensure we don't return until the resize has actually happened,
650  // or 500ms have passed.
651 
652  boolean bShouldWait = false;
653 
654  if (data instanceof Integer) {
655  // Let's figure out if we're already laid out fullscreen or not.
656  Display display = ((WindowManager)getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
657  android.util.DisplayMetrics realMetrics = new android.util.DisplayMetrics();
658  display.getRealMetrics( realMetrics );
659 
660  boolean bFullscreenLayout = ((realMetrics.widthPixels == mSurface.getWidth()) &&
661  (realMetrics.heightPixels == mSurface.getHeight()));
662 
663  if (((Integer)data).intValue() == 1) {
664  // If we aren't laid out fullscreen or actively in fullscreen mode already, we're going
665  // to change size and should wait for surfaceChanged() before we return, so the size
666  // is right back in native code. If we're already laid out fullscreen, though, we're
667  // not going to change size even if we change decor modes, so we shouldn't wait for
668  // surfaceChanged() -- which may not even happen -- and should return immediately.
669  bShouldWait = !bFullscreenLayout;
670  }
671  else {
672  // If we're laid out fullscreen (even if the status bar and nav bar are present),
673  // or are actively in fullscreen, we're going to change size and should wait for
674  // surfaceChanged before we return, so the size is right back in native code.
675  bShouldWait = bFullscreenLayout;
676  }
677  }
678 
679  if (bShouldWait) {
680  // We'll wait for the surfaceChanged() method, which will notify us
681  // when called. That way, we know our current size is really the
682  // size we need, instead of grabbing a size that's still got
683  // the navigation and/or status bars before they're hidden.
684  //
685  // We'll wait for up to half a second, because some devices
686  // take a surprisingly long time for the surface resize, but
687  // then we'll just give up and return.
688  //
689  synchronized(SDLActivity.getContext()) {
690  try {
691  SDLActivity.getContext().wait(500);
692  }
693  catch (InterruptedException ie) {
694  ie.printStackTrace();
695  }
696  }
697  }
698  }
699 
700  return result;
701  }
702 
703  // C functions we call
704  public static native int nativeSetupJNI();
705  public static native int nativeRunMain(String library, String function, Object arguments);
706  public static native void nativeLowMemory();
707  public static native void nativeQuit();
708  public static native void nativePause();
709  public static native void nativeResume();
710  public static native void onNativeDropFile(String filename);
711  public static native void onNativeResize(int surfaceWidth, int surfaceHeight, int deviceWidth, int deviceHeight, int format, float rate);
712  public static native void onNativeKeyDown(int keycode);
713  public static native void onNativeKeyUp(int keycode);
714  public static native void onNativeKeyboardFocusLost();
715  public static native void onNativeMouse(int button, int action, float x, float y, boolean relative);
716  public static native void onNativeTouch(int touchDevId, int pointerFingerId,
717  int action, float x,
718  float y, float p);
719  public static native void onNativeAccel(float x, float y, float z);
720  public static native void onNativeClipboardChanged();
721  public static native void onNativeSurfaceChanged();
722  public static native void onNativeSurfaceDestroyed();
723  public static native String nativeGetHint(String name);
724  public static native void nativeSetenv(String name, String value);
725  public static native void onNativeOrientationChanged(int orientation);
726 
727  /**
728  * This method is called by SDL using JNI.
729  */
730  public static boolean setActivityTitle(String title) {
731  // Called from SDLMain() thread and can't directly affect the view
732  return mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title);
733  }
734 
735  /**
736  * This method is called by SDL using JNI.
737  */
738  public static void setWindowStyle(boolean fullscreen) {
739  // Called from SDLMain() thread and can't directly affect the view
740  mSingleton.sendCommand(COMMAND_CHANGE_WINDOW_STYLE, fullscreen ? 1 : 0);
741  }
742 
743  /**
744  * This method is called by SDL using JNI.
745  * This is a static method for JNI convenience, it calls a non-static method
746  * so that is can be overridden
747  */
748  public static void setOrientation(int w, int h, boolean resizable, String hint)
749  {
750  if (mSingleton != null) {
751  mSingleton.setOrientationBis(w, h, resizable, hint);
752  }
753  }
754 
755  /**
756  * This can be overridden
757  */
758  public void setOrientationBis(int w, int h, boolean resizable, String hint)
759  {
760  int orientation = -1;
761 
762  if (hint.contains("LandscapeRight") && hint.contains("LandscapeLeft")) {
763  orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
764  } else if (hint.contains("LandscapeRight")) {
765  orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
766  } else if (hint.contains("LandscapeLeft")) {
767  orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
768  } else if (hint.contains("Portrait") && hint.contains("PortraitUpsideDown")) {
769  orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT;
770  } else if (hint.contains("Portrait")) {
771  orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
772  } else if (hint.contains("PortraitUpsideDown")) {
773  orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
774  }
775 
776  /* no valid hint */
777  if (orientation == -1) {
778  if (resizable) {
779  /* no fixed orientation */
780  } else {
781  if (w > h) {
782  orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
783  } else {
784  orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT;
785  }
786  }
787  }
788 
789  Log.v("SDL", "setOrientation() orientation=" + orientation + " width=" + w +" height="+ h +" resizable=" + resizable + " hint=" + hint);
790  if (orientation != -1) {
791  mSingleton.setRequestedOrientation(orientation);
792  }
793  }
794 
795  /**
796  * This method is called by SDL using JNI.
797  */
798  public static boolean isScreenKeyboardShown()
799  {
800  if (mTextEdit == null) {
801  return false;
802  }
803 
804  if (!mScreenKeyboardShown) {
805  return false;
806  }
807 
808  InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
809  return imm.isAcceptingText();
810 
811  }
812 
813  /**
814  * This method is called by SDL using JNI.
815  */
816  public static boolean supportsRelativeMouse()
817  {
818  // ChromeOS doesn't provide relative mouse motion via the Android 7 APIs
819  if (isChromebook()) {
820  return false;
821  }
822 
823  // DeX mode in Samsung Experience 9.0 and earlier doesn't support relative mice properly under
824  // Android 7 APIs, and simply returns no data under Android 8 APIs.
825  //
826  // This is fixed in Samsung Experience 9.5, which corresponds to Android 8.1.0, and
827  // thus SDK version 27. If we are in DeX mode and not API 27 or higher, as a result,
828  // we should stick to relative mode.
829  //
830  if ((Build.VERSION.SDK_INT < 27) && isDeXMode()) {
831  return false;
832  }
833 
834  return SDLActivity.getMotionListener().supportsRelativeMouse();
835  }
836 
837  /**
838  * This method is called by SDL using JNI.
839  */
840  public static boolean setRelativeMouseEnabled(boolean enabled)
841  {
842  if (enabled && !supportsRelativeMouse()) {
843  return false;
844  }
845 
846  return SDLActivity.getMotionListener().setRelativeMouseEnabled(enabled);
847  }
848 
849  /**
850  * This method is called by SDL using JNI.
851  */
852  public static boolean sendMessage(int command, int param) {
853  if (mSingleton == null) {
854  return false;
855  }
856  return mSingleton.sendCommand(command, Integer.valueOf(param));
857  }
858 
859  /**
860  * This method is called by SDL using JNI.
861  */
862  public static Context getContext() {
863  return SDL.getContext();
864  }
865 
866  /**
867  * This method is called by SDL using JNI.
868  */
869  public static boolean isAndroidTV() {
870  UiModeManager uiModeManager = (UiModeManager) getContext().getSystemService(UI_MODE_SERVICE);
871  if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) {
872  return true;
873  }
874  if (Build.MANUFACTURER.equals("MINIX") && Build.MODEL.equals("NEO-U1")) {
875  return true;
876  }
877  if (Build.MANUFACTURER.equals("Amlogic") && Build.MODEL.equals("X96-W")) {
878  return true;
879  }
880  return false;
881  }
882 
883  /**
884  * This method is called by SDL using JNI.
885  */
886  public static boolean isTablet() {
887  DisplayMetrics metrics = new DisplayMetrics();
888  Activity activity = (Activity)getContext();
889  activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
890 
891  double dWidthInches = metrics.widthPixels / (double)metrics.xdpi;
892  double dHeightInches = metrics.heightPixels / (double)metrics.ydpi;
893 
894  double dDiagonal = Math.sqrt((dWidthInches * dWidthInches) + (dHeightInches * dHeightInches));
895 
896  // If our diagonal size is seven inches or greater, we consider ourselves a tablet.
897  return (dDiagonal >= 7.0);
898  }
899 
900  /**
901  * This method is called by SDL using JNI.
902  */
903  public static boolean isChromebook() {
904  return getContext().getPackageManager().hasSystemFeature("org.chromium.arc.device_management");
905  }
906 
907  /**
908  * This method is called by SDL using JNI.
909  */
910  public static boolean isDeXMode() {
911  if (Build.VERSION.SDK_INT < 24) {
912  return false;
913  }
914  try {
915  final Configuration config = getContext().getResources().getConfiguration();
916  final Class configClass = config.getClass();
917  return configClass.getField("SEM_DESKTOP_MODE_ENABLED").getInt(configClass)
918  == configClass.getField("semDesktopModeEnabled").getInt(config);
919  } catch(Exception ignored) {
920  return false;
921  }
922  }
923 
924  /**
925  * This method is called by SDL using JNI.
926  */
927  public static DisplayMetrics getDisplayDPI() {
928  return getContext().getResources().getDisplayMetrics();
929  }
930 
931  /**
932  * This method is called by SDL using JNI.
933  */
934  public static boolean getManifestEnvironmentVariables() {
935  try {
936  ApplicationInfo applicationInfo = getContext().getPackageManager().getApplicationInfo(getContext().getPackageName(), PackageManager.GET_META_DATA);
937  Bundle bundle = applicationInfo.metaData;
938  if (bundle == null) {
939  return false;
940  }
941  String prefix = "SDL_ENV.";
942  final int trimLength = prefix.length();
943  for (String key : bundle.keySet()) {
944  if (key.startsWith(prefix)) {
945  String name = key.substring(trimLength);
946  String value = bundle.get(key).toString();
947  nativeSetenv(name, value);
948  }
949  }
950  /* environment variables set! */
951  return true;
952  } catch (Exception e) {
953  Log.v("SDL", "exception " + e.toString());
954  }
955  return false;
956  }
957 
958  // This method is called by SDLControllerManager's API 26 Generic Motion Handler.
959  public static View getContentView()
960  {
961  return mSingleton.mLayout;
962  }
963 
964  static class ShowTextInputTask implements Runnable {
965  /*
966  * This is used to regulate the pan&scan method to have some offset from
967  * the bottom edge of the input region and the top edge of an input
968  * method (soft keyboard)
969  */
970  static final int HEIGHT_PADDING = 15;
971 
972  public int x, y, w, h;
973 
974  public ShowTextInputTask(int x, int y, int w, int h) {
975  this.x = x;
976  this.y = y;
977  this.w = w;
978  this.h = h;
979  }
980 
981  @Override
982  public void run() {
983  RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(w, h + HEIGHT_PADDING);
984  params.leftMargin = x;
985  params.topMargin = y;
986 
987  if (mTextEdit == null) {
988  mTextEdit = new DummyEdit(SDL.getContext());
989 
990  mLayout.addView(mTextEdit, params);
991  } else {
992  mTextEdit.setLayoutParams(params);
993  }
994 
995  mTextEdit.setVisibility(View.VISIBLE);
996  mTextEdit.requestFocus();
997 
998  InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
999  imm.showSoftInput(mTextEdit, 0);
1000 
1001  mScreenKeyboardShown = true;
1002  }
1003  }
1004 
1005  /**
1006  * This method is called by SDL using JNI.
1007  */
1008  public static boolean showTextInput(int x, int y, int w, int h) {
1009  // Transfer the task to the main thread as a Runnable
1010  return mSingleton.commandHandler.post(new ShowTextInputTask(x, y, w, h));
1011  }
1012 
1013  public static boolean isTextInputEvent(KeyEvent event) {
1014 
1015  // Key pressed with Ctrl should be sent as SDL_KEYDOWN/SDL_KEYUP and not SDL_TEXTINPUT
1016  if (Build.VERSION.SDK_INT >= 11) {
1017  if (event.isCtrlPressed()) {
1018  return false;
1019  }
1020  }
1021 
1022  return event.isPrintingKey() || event.getKeyCode() == KeyEvent.KEYCODE_SPACE;
1023  }
1024 
1025  /**
1026  * This method is called by SDL using JNI.
1027  */
1028  public static Surface getNativeSurface() {
1029  if (SDLActivity.mSurface == null) {
1030  return null;
1031  }
1032  return SDLActivity.mSurface.getNativeSurface();
1033  }
1034 
1035  // Input
1036 
1037  /**
1038  * This method is called by SDL using JNI.
1039  * @return an array which may be empty but is never null.
1040  */
1041  public static int[] inputGetInputDeviceIds(int sources) {
1042  int[] ids = InputDevice.getDeviceIds();
1043  int[] filtered = new int[ids.length];
1044  int used = 0;
1045  for (int i = 0; i < ids.length; ++i) {
1046  InputDevice device = InputDevice.getDevice(ids[i]);
1047  if ((device != null) && ((device.getSources() & sources) != 0)) {
1048  filtered[used++] = device.getId();
1049  }
1050  }
1051  return Arrays.copyOf(filtered, used);
1052  }
1053 
1054  // APK expansion files support
1055 
1056  /** com.android.vending.expansion.zipfile.ZipResourceFile object or null. */
1057  private static Object expansionFile;
1058 
1059  /** com.android.vending.expansion.zipfile.ZipResourceFile's getInputStream() or null. */
1060  private static Method expansionFileMethod;
1061 
1062  /**
1063  * This method is called by SDL using JNI.
1064  * @return an InputStream on success or null if no expansion file was used.
1065  * @throws IOException on errors. Message is set for the SDL error message.
1066  */
1067  public static InputStream openAPKExpansionInputStream(String fileName) throws IOException {
1068  // Get a ZipResourceFile representing a merger of both the main and patch files
1069  if (expansionFile == null) {
1070  String mainHint = nativeGetHint("SDL_ANDROID_APK_EXPANSION_MAIN_FILE_VERSION");
1071  if (mainHint == null) {
1072  return null; // no expansion use if no main version was set
1073  }
1074  String patchHint = nativeGetHint("SDL_ANDROID_APK_EXPANSION_PATCH_FILE_VERSION");
1075  if (patchHint == null) {
1076  return null; // no expansion use if no patch version was set
1077  }
1078 
1079  Integer mainVersion;
1080  Integer patchVersion;
1081  try {
1082  mainVersion = Integer.valueOf(mainHint);
1083  patchVersion = Integer.valueOf(patchHint);
1084  } catch (NumberFormatException ex) {
1085  ex.printStackTrace();
1086  throw new IOException("No valid file versions set for APK expansion files", ex);
1087  }
1088 
1089  try {
1090  // To avoid direct dependency on Google APK expansion library that is
1091  // not a part of Android SDK we access it using reflection
1092  expansionFile = Class.forName("com.android.vending.expansion.zipfile.APKExpansionSupport")
1093  .getMethod("getAPKExpansionZipFile", Context.class, int.class, int.class)
1094  .invoke(null, SDL.getContext(), mainVersion, patchVersion);
1095 
1096  expansionFileMethod = expansionFile.getClass()
1097  .getMethod("getInputStream", String.class);
1098  } catch (Exception ex) {
1099  ex.printStackTrace();
1100  expansionFile = null;
1101  expansionFileMethod = null;
1102  throw new IOException("Could not access APK expansion support library", ex);
1103  }
1104  }
1105 
1106  // Get an input stream for a known file inside the expansion file ZIPs
1107  InputStream fileStream;
1108  try {
1109  fileStream = (InputStream)expansionFileMethod.invoke(expansionFile, fileName);
1110  } catch (Exception ex) {
1111  // calling "getInputStream" failed
1112  ex.printStackTrace();
1113  throw new IOException("Could not open stream from APK expansion file", ex);
1114  }
1115 
1116  if (fileStream == null) {
1117  // calling "getInputStream" was successful but null was returned
1118  throw new IOException("Could not find path in APK expansion file");
1119  }
1120 
1121  return fileStream;
1122  }
1123 
1124  // Messagebox
1125 
1126  /** Result of current messagebox. Also used for blocking the calling thread. */
1127  protected final int[] messageboxSelection = new int[1];
1128 
1129  /** Id of current dialog. */
1130  protected int dialogs = 0;
1131 
1132  /**
1133  * This method is called by SDL using JNI.
1134  * Shows the messagebox from UI thread and block calling thread.
1135  * buttonFlags, buttonIds and buttonTexts must have same length.
1136  * @param buttonFlags array containing flags for every button.
1137  * @param buttonIds array containing id for every button.
1138  * @param buttonTexts array containing text for every button.
1139  * @param colors null for default or array of length 5 containing colors.
1140  * @return button id or -1.
1141  */
1143  final int flags,
1144  final String title,
1145  final String message,
1146  final int[] buttonFlags,
1147  final int[] buttonIds,
1148  final String[] buttonTexts,
1149  final int[] colors) {
1150 
1151  messageboxSelection[0] = -1;
1152 
1153  // sanity checks
1154 
1155  if ((buttonFlags.length != buttonIds.length) && (buttonIds.length != buttonTexts.length)) {
1156  return -1; // implementation broken
1157  }
1158 
1159  // collect arguments for Dialog
1160 
1161  final Bundle args = new Bundle();
1162  args.putInt("flags", flags);
1163  args.putString("title", title);
1164  args.putString("message", message);
1165  args.putIntArray("buttonFlags", buttonFlags);
1166  args.putIntArray("buttonIds", buttonIds);
1167  args.putStringArray("buttonTexts", buttonTexts);
1168  args.putIntArray("colors", colors);
1169 
1170  // trigger Dialog creation on UI thread
1171 
1172  runOnUiThread(new Runnable() {
1173  @Override
1174  public void run() {
1175  showDialog(dialogs++, args);
1176  }
1177  });
1178 
1179  // block the calling thread
1180 
1181  synchronized (messageboxSelection) {
1182  try {
1183  messageboxSelection.wait();
1184  } catch (InterruptedException ex) {
1185  ex.printStackTrace();
1186  return -1;
1187  }
1188  }
1189 
1190  // return selected value
1191 
1192  return messageboxSelection[0];
1193  }
1194 
1195  @Override
1196  protected Dialog onCreateDialog(int ignore, Bundle args) {
1197 
1198  // TODO set values from "flags" to messagebox dialog
1199 
1200  // get colors
1201 
1202  int[] colors = args.getIntArray("colors");
1203  int backgroundColor;
1204  int textColor;
1205  int buttonBorderColor;
1206  int buttonBackgroundColor;
1207  int buttonSelectedColor;
1208  if (colors != null) {
1209  int i = -1;
1210  backgroundColor = colors[++i];
1211  textColor = colors[++i];
1212  buttonBorderColor = colors[++i];
1213  buttonBackgroundColor = colors[++i];
1214  buttonSelectedColor = colors[++i];
1215  } else {
1216  backgroundColor = Color.TRANSPARENT;
1217  textColor = Color.TRANSPARENT;
1218  buttonBorderColor = Color.TRANSPARENT;
1219  buttonBackgroundColor = Color.TRANSPARENT;
1220  buttonSelectedColor = Color.TRANSPARENT;
1221  }
1222 
1223  // create dialog with title and a listener to wake up calling thread
1224 
1225  final Dialog dialog = new Dialog(this);
1226  dialog.setTitle(args.getString("title"));
1227  dialog.setCancelable(false);
1228  dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
1229  @Override
1230  public void onDismiss(DialogInterface unused) {
1231  synchronized (messageboxSelection) {
1232  messageboxSelection.notify();
1233  }
1234  }
1235  });
1236 
1237  // create text
1238 
1239  TextView message = new TextView(this);
1240  message.setGravity(Gravity.CENTER);
1241  message.setText(args.getString("message"));
1242  if (textColor != Color.TRANSPARENT) {
1243  message.setTextColor(textColor);
1244  }
1245 
1246  // create buttons
1247 
1248  int[] buttonFlags = args.getIntArray("buttonFlags");
1249  int[] buttonIds = args.getIntArray("buttonIds");
1250  String[] buttonTexts = args.getStringArray("buttonTexts");
1251 
1252  final SparseArray<Button> mapping = new SparseArray<Button>();
1253 
1254  LinearLayout buttons = new LinearLayout(this);
1255  buttons.setOrientation(LinearLayout.HORIZONTAL);
1256  buttons.setGravity(Gravity.CENTER);
1257  for (int i = 0; i < buttonTexts.length; ++i) {
1258  Button button = new Button(this);
1259  final int id = buttonIds[i];
1260  button.setOnClickListener(new View.OnClickListener() {
1261  @Override
1262  public void onClick(View v) {
1263  messageboxSelection[0] = id;
1264  dialog.dismiss();
1265  }
1266  });
1267  if (buttonFlags[i] != 0) {
1268  // see SDL_messagebox.h
1269  if ((buttonFlags[i] & 0x00000001) != 0) {
1270  mapping.put(KeyEvent.KEYCODE_ENTER, button);
1271  }
1272  if ((buttonFlags[i] & 0x00000002) != 0) {
1273  mapping.put(KeyEvent.KEYCODE_ESCAPE, button); /* API 11 */
1274  }
1275  }
1276  button.setText(buttonTexts[i]);
1277  if (textColor != Color.TRANSPARENT) {
1278  button.setTextColor(textColor);
1279  }
1280  if (buttonBorderColor != Color.TRANSPARENT) {
1281  // TODO set color for border of messagebox button
1282  }
1283  if (buttonBackgroundColor != Color.TRANSPARENT) {
1284  Drawable drawable = button.getBackground();
1285  if (drawable == null) {
1286  // setting the color this way removes the style
1287  button.setBackgroundColor(buttonBackgroundColor);
1288  } else {
1289  // setting the color this way keeps the style (gradient, padding, etc.)
1290  drawable.setColorFilter(buttonBackgroundColor, PorterDuff.Mode.MULTIPLY);
1291  }
1292  }
1293  if (buttonSelectedColor != Color.TRANSPARENT) {
1294  // TODO set color for selected messagebox button
1295  }
1296  buttons.addView(button);
1297  }
1298 
1299  // create content
1300 
1301  LinearLayout content = new LinearLayout(this);
1302  content.setOrientation(LinearLayout.VERTICAL);
1303  content.addView(message);
1304  content.addView(buttons);
1305  if (backgroundColor != Color.TRANSPARENT) {
1306  content.setBackgroundColor(backgroundColor);
1307  }
1308 
1309  // add content to dialog and return
1310 
1311  dialog.setContentView(content);
1312  dialog.setOnKeyListener(new Dialog.OnKeyListener() {
1313  @Override
1314  public boolean onKey(DialogInterface d, int keyCode, KeyEvent event) {
1315  Button button = mapping.get(keyCode);
1316  if (button != null) {
1317  if (event.getAction() == KeyEvent.ACTION_UP) {
1318  button.performClick();
1319  }
1320  return true; // also for ignored actions
1321  }
1322  return false;
1323  }
1324  });
1325 
1326  return dialog;
1327  }
1328 
1329  private final Runnable rehideSystemUi = new Runnable() {
1330  @Override
1331  public void run() {
1332  int flags = View.SYSTEM_UI_FLAG_FULLSCREEN |
1333  View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
1334  View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY |
1335  View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
1336  View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
1337  View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.INVISIBLE;
1338 
1339  SDLActivity.this.getWindow().getDecorView().setSystemUiVisibility(flags);
1340  }
1341  };
1342 
1343  public void onSystemUiVisibilityChange(int visibility) {
1344  if (SDLActivity.mFullscreenModeActive && (visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0 || (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) {
1345 
1346  Handler handler = getWindow().getDecorView().getHandler();
1347  if (handler != null) {
1348  handler.removeCallbacks(rehideSystemUi); // Prevent a hide loop.
1349  handler.postDelayed(rehideSystemUi, 2000);
1350  }
1351 
1352  }
1353  }
1354 
1355  /**
1356  * This method is called by SDL using JNI.
1357  */
1358  public static boolean clipboardHasText() {
1359  return mClipboardHandler.clipboardHasText();
1360  }
1361 
1362  /**
1363  * This method is called by SDL using JNI.
1364  */
1365  public static String clipboardGetText() {
1366  return mClipboardHandler.clipboardGetText();
1367  }
1368 
1369  /**
1370  * This method is called by SDL using JNI.
1371  */
1372  public static void clipboardSetText(String string) {
1373  mClipboardHandler.clipboardSetText(string);
1374  }
1375 
1376  /**
1377  * This method is called by SDL using JNI.
1378  */
1379  public static int createCustomCursor(int[] colors, int width, int height, int hotSpotX, int hotSpotY) {
1380  Bitmap bitmap = Bitmap.createBitmap(colors, width, height, Bitmap.Config.ARGB_8888);
1381  ++mLastCursorID;
1382  // This requires API 24, so use reflection to implement this
1383  try {
1384  Class PointerIconClass = Class.forName("android.view.PointerIcon");
1385  Class[] arg_types = new Class[] { Bitmap.class, float.class, float.class };
1386  Method create = PointerIconClass.getMethod("create", arg_types);
1387  mCursors.put(mLastCursorID, create.invoke(null, bitmap, hotSpotX, hotSpotY));
1388  } catch (Exception e) {
1389  return 0;
1390  }
1391  return mLastCursorID;
1392  }
1393 
1394  /**
1395  * This method is called by SDL using JNI.
1396  */
1397  public static boolean setCustomCursor(int cursorID) {
1398  // This requires API 24, so use reflection to implement this
1399  try {
1400  Class PointerIconClass = Class.forName("android.view.PointerIcon");
1401  Method setPointerIcon = SDLSurface.class.getMethod("setPointerIcon", PointerIconClass);
1402  setPointerIcon.invoke(mSurface, mCursors.get(cursorID));
1403  } catch (Exception e) {
1404  return false;
1405  }
1406  return true;
1407  }
1408 
1409  /**
1410  * This method is called by SDL using JNI.
1411  */
1412  public static boolean setSystemCursor(int cursorID) {
1413  int cursor_type = 0; //PointerIcon.TYPE_NULL;
1414  switch (cursorID) {
1416  cursor_type = 1000; //PointerIcon.TYPE_ARROW;
1417  break;
1419  cursor_type = 1008; //PointerIcon.TYPE_TEXT;
1420  break;
1422  cursor_type = 1004; //PointerIcon.TYPE_WAIT;
1423  break;
1425  cursor_type = 1007; //PointerIcon.TYPE_CROSSHAIR;
1426  break;
1428  cursor_type = 1004; //PointerIcon.TYPE_WAIT;
1429  break;
1431  cursor_type = 1017; //PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW;
1432  break;
1434  cursor_type = 1016; //PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW;
1435  break;
1437  cursor_type = 1014; //PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
1438  break;
1440  cursor_type = 1015; //PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
1441  break;
1443  cursor_type = 1020; //PointerIcon.TYPE_GRAB;
1444  break;
1445  case SDL_SYSTEM_CURSOR_NO:
1446  cursor_type = 1012; //PointerIcon.TYPE_NO_DROP;
1447  break;
1449  cursor_type = 1002; //PointerIcon.TYPE_HAND;
1450  break;
1451  }
1452  // This requires API 24, so use reflection to implement this
1453  try {
1454  Class PointerIconClass = Class.forName("android.view.PointerIcon");
1455  Class[] arg_types = new Class[] { Context.class, int.class };
1456  Method getSystemIcon = PointerIconClass.getMethod("getSystemIcon", arg_types);
1457  Method setPointerIcon = SDLSurface.class.getMethod("setPointerIcon", PointerIconClass);
1458  setPointerIcon.invoke(mSurface, getSystemIcon.invoke(null, SDL.getContext(), cursor_type));
1459  } catch (Exception e) {
1460  return false;
1461  }
1462  return true;
1463  }
1464 }
1465 
1466 /**
1467  Simple runnable to start the SDL application
1468 */
1469 class SDLMain implements Runnable {
1470  @Override
1471  public void run() {
1472  // Runs SDL_main()
1473  String library = SDLActivity.mSingleton.getMainSharedObject();
1474  String function = SDLActivity.mSingleton.getMainFunction();
1475  String[] arguments = SDLActivity.mSingleton.getArguments();
1476 
1477  Log.v("SDL", "Running main function " + function + " from library " + library);
1478  SDLActivity.nativeRunMain(library, function, arguments);
1479 
1480  Log.v("SDL", "Finished main function");
1481 
1482  // Native thread has finished, let's finish the Activity
1485  }
1486  }
1487 }
1488 
1489 
1490 /**
1491  SDLSurface. This is what we draw on, so we need to know when it's created
1492  in order to do anything useful.
1493 
1494  Because of this, that's where we set up the SDL thread
1495 */
1496 class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
1497  View.OnKeyListener, View.OnTouchListener, SensorEventListener {
1498 
1499  // Sensors
1500  protected static SensorManager mSensorManager;
1501  protected static Display mDisplay;
1502 
1503  // Keep track of the surface size to normalize touch events
1504  protected static float mWidth, mHeight;
1505 
1506  // Startup
1507  public SDLSurface(Context context) {
1508  super(context);
1509  getHolder().addCallback(this);
1510 
1511  setFocusable(true);
1512  setFocusableInTouchMode(true);
1513  requestFocus();
1514  setOnKeyListener(this);
1515  setOnTouchListener(this);
1516 
1517  mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
1518  mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
1519 
1520  if (Build.VERSION.SDK_INT >= 12) {
1521  setOnGenericMotionListener(SDLActivity.getMotionListener());
1522  }
1523 
1524  // Some arbitrary defaults to avoid a potential division by zero
1525  mWidth = 1.0f;
1526  mHeight = 1.0f;
1527  }
1528 
1529  public void handlePause() {
1530  enableSensor(Sensor.TYPE_ACCELEROMETER, false);
1531  }
1532 
1533  public void handleResume() {
1534  setFocusable(true);
1535  setFocusableInTouchMode(true);
1536  requestFocus();
1537  setOnKeyListener(this);
1538  setOnTouchListener(this);
1539  enableSensor(Sensor.TYPE_ACCELEROMETER, true);
1540  }
1541 
1542  public Surface getNativeSurface() {
1543  return getHolder().getSurface();
1544  }
1545 
1546  // Called when we have a valid drawing surface
1547  @Override
1548  public void surfaceCreated(SurfaceHolder holder) {
1549  Log.v("SDL", "surfaceCreated()");
1550  holder.setType(SurfaceHolder.SURFACE_TYPE_GPU);
1551  }
1552 
1553  // Called when we lose the surface
1554  @Override
1555  public void surfaceDestroyed(SurfaceHolder holder) {
1556  Log.v("SDL", "surfaceDestroyed()");
1557 
1558  // Transition to pause, if needed
1561 
1562  SDLActivity.mIsSurfaceReady = false;
1564  }
1565 
1566  // Called when the surface is resized
1567  @Override
1568  public void surfaceChanged(SurfaceHolder holder,
1569  int format, int width, int height) {
1570  Log.v("SDL", "surfaceChanged()");
1571 
1572  if (SDLActivity.mSingleton == null) {
1573  return;
1574  }
1575 
1576  int sdlFormat = 0x15151002; // SDL_PIXELFORMAT_RGB565 by default
1577  switch (format) {
1578  case PixelFormat.A_8:
1579  Log.v("SDL", "pixel format A_8");
1580  break;
1581  case PixelFormat.LA_88:
1582  Log.v("SDL", "pixel format LA_88");
1583  break;
1584  case PixelFormat.L_8:
1585  Log.v("SDL", "pixel format L_8");
1586  break;
1587  case PixelFormat.RGBA_4444:
1588  Log.v("SDL", "pixel format RGBA_4444");
1589  sdlFormat = 0x15421002; // SDL_PIXELFORMAT_RGBA4444
1590  break;
1591  case PixelFormat.RGBA_5551:
1592  Log.v("SDL", "pixel format RGBA_5551");
1593  sdlFormat = 0x15441002; // SDL_PIXELFORMAT_RGBA5551
1594  break;
1595  case PixelFormat.RGBA_8888:
1596  Log.v("SDL", "pixel format RGBA_8888");
1597  sdlFormat = 0x16462004; // SDL_PIXELFORMAT_RGBA8888
1598  break;
1599  case PixelFormat.RGBX_8888:
1600  Log.v("SDL", "pixel format RGBX_8888");
1601  sdlFormat = 0x16261804; // SDL_PIXELFORMAT_RGBX8888
1602  break;
1603  case PixelFormat.RGB_332:
1604  Log.v("SDL", "pixel format RGB_332");
1605  sdlFormat = 0x14110801; // SDL_PIXELFORMAT_RGB332
1606  break;
1607  case PixelFormat.RGB_565:
1608  Log.v("SDL", "pixel format RGB_565");
1609  sdlFormat = 0x15151002; // SDL_PIXELFORMAT_RGB565
1610  break;
1611  case PixelFormat.RGB_888:
1612  Log.v("SDL", "pixel format RGB_888");
1613  // Not sure this is right, maybe SDL_PIXELFORMAT_RGB24 instead?
1614  sdlFormat = 0x16161804; // SDL_PIXELFORMAT_RGB888
1615  break;
1616  default:
1617  Log.v("SDL", "pixel format unknown " + format);
1618  break;
1619  }
1620 
1621  mWidth = width;
1622  mHeight = height;
1623  int nDeviceWidth = width;
1624  int nDeviceHeight = height;
1625  try
1626  {
1627  if (Build.VERSION.SDK_INT >= 17) {
1628  android.util.DisplayMetrics realMetrics = new android.util.DisplayMetrics();
1629  mDisplay.getRealMetrics( realMetrics );
1630  nDeviceWidth = realMetrics.widthPixels;
1631  nDeviceHeight = realMetrics.heightPixels;
1632  }
1633  }
1634  catch ( java.lang.Throwable throwable ) {}
1635 
1636  synchronized(SDLActivity.getContext()) {
1637  // In case we're waiting on a size change after going fullscreen, send a notification.
1638  SDLActivity.getContext().notifyAll();
1639  }
1640 
1641  Log.v("SDL", "Window size: " + width + "x" + height);
1642  Log.v("SDL", "Device size: " + nDeviceWidth + "x" + nDeviceHeight);
1643  SDLActivity.onNativeResize(width, height, nDeviceWidth, nDeviceHeight, sdlFormat, mDisplay.getRefreshRate());
1644 
1645  boolean skip = false;
1646  int requestedOrientation = SDLActivity.mSingleton.getRequestedOrientation();
1647 
1648  if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)
1649  {
1650  // Accept any
1651  }
1652  else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT)
1653  {
1654  if (mWidth > mHeight) {
1655  skip = true;
1656  }
1657  } else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) {
1658  if (mWidth < mHeight) {
1659  skip = true;
1660  }
1661  }
1662 
1663  // Special Patch for Square Resolution: Black Berry Passport
1664  if (skip) {
1665  double min = Math.min(mWidth, mHeight);
1666  double max = Math.max(mWidth, mHeight);
1667 
1668  if (max / min < 1.20) {
1669  Log.v("SDL", "Don't skip on such aspect-ratio. Could be a square resolution.");
1670  skip = false;
1671  }
1672  }
1673 
1674  if (skip) {
1675  Log.v("SDL", "Skip .. Surface is not ready.");
1676  SDLActivity.mIsSurfaceReady = false;
1677  return;
1678  }
1679 
1680  /* Surface is ready */
1681  SDLActivity.mIsSurfaceReady = true;
1682 
1683  /* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */
1685 
1687  }
1688 
1689  // Key events
1690  @Override
1691  public boolean onKey(View v, int keyCode, KeyEvent event) {
1692  // Dispatch the different events depending on where they come from
1693  // Some SOURCE_JOYSTICK, SOURCE_DPAD or SOURCE_GAMEPAD are also SOURCE_KEYBOARD
1694  // So, we try to process them as JOYSTICK/DPAD/GAMEPAD events first, if that fails we try them as KEYBOARD
1695  //
1696  // Furthermore, it's possible a game controller has SOURCE_KEYBOARD and
1697  // SOURCE_JOYSTICK, while its key events arrive from the keyboard source
1698  // So, retrieve the device itself and check all of its sources
1699  if (SDLControllerManager.isDeviceSDLJoystick(event.getDeviceId())) {
1700  // Note that we process events with specific key codes here
1701  if (event.getAction() == KeyEvent.ACTION_DOWN) {
1702  if (SDLControllerManager.onNativePadDown(event.getDeviceId(), keyCode) == 0) {
1703  return true;
1704  }
1705  } else if (event.getAction() == KeyEvent.ACTION_UP) {
1706  if (SDLControllerManager.onNativePadUp(event.getDeviceId(), keyCode) == 0) {
1707  return true;
1708  }
1709  }
1710  }
1711 
1712  if ((event.getSource() & InputDevice.SOURCE_KEYBOARD) != 0) {
1713  if (event.getAction() == KeyEvent.ACTION_DOWN) {
1714  //Log.v("SDL", "key down: " + keyCode);
1715  if (SDLActivity.isTextInputEvent(event)) {
1716  SDLInputConnection.nativeCommitText(String.valueOf((char) event.getUnicodeChar()), 1);
1717  }
1718  SDLActivity.onNativeKeyDown(keyCode);
1719  return true;
1720  }
1721  else if (event.getAction() == KeyEvent.ACTION_UP) {
1722  //Log.v("SDL", "key up: " + keyCode);
1723  SDLActivity.onNativeKeyUp(keyCode);
1724  return true;
1725  }
1726  }
1727 
1728  if ((event.getSource() & InputDevice.SOURCE_MOUSE) != 0) {
1729  // on some devices key events are sent for mouse BUTTON_BACK/FORWARD presses
1730  // they are ignored here because sending them as mouse input to SDL is messy
1731  if ((keyCode == KeyEvent.KEYCODE_BACK) || (keyCode == KeyEvent.KEYCODE_FORWARD)) {
1732  switch (event.getAction()) {
1733  case KeyEvent.ACTION_DOWN:
1734  case KeyEvent.ACTION_UP:
1735  // mark the event as handled or it will be handled by system
1736  // handling KEYCODE_BACK by system will call onBackPressed()
1737  return true;
1738  }
1739  }
1740  }
1741 
1742  return false;
1743  }
1744 
1745  // Touch events
1746  @Override
1747  public boolean onTouch(View v, MotionEvent event) {
1748  /* Ref: http://developer.android.com/training/gestures/multi.html */
1749  final int touchDevId = event.getDeviceId();
1750  final int pointerCount = event.getPointerCount();
1751  int action = event.getActionMasked();
1752  int pointerFingerId;
1753  int mouseButton;
1754  int i = -1;
1755  float x,y,p;
1756 
1757  // !!! FIXME: dump this SDK check after 2.0.4 ships and require API14.
1758  // 12290 = Samsung DeX mode desktop mouse
1759  if ((event.getSource() == InputDevice.SOURCE_MOUSE || event.getSource() == 12290) && SDLActivity.mSeparateMouseAndTouch) {
1760  if (Build.VERSION.SDK_INT < 14) {
1761  mouseButton = 1; // all mouse buttons are the left button
1762  } else {
1763  try {
1764  mouseButton = (Integer) event.getClass().getMethod("getButtonState").invoke(event);
1765  } catch(Exception e) {
1766  mouseButton = 1; // oh well.
1767  }
1768  }
1769 
1770  // We need to check if we're in relative mouse mode and get the axis offset rather than the x/y values
1771  // if we are. We'll leverage our existing mouse motion listener
1772  SDLGenericMotionListener_API12 motionListener = SDLActivity.getMotionListener();
1773  x = motionListener.getEventX(event);
1774  y = motionListener.getEventY(event);
1775 
1776  SDLActivity.onNativeMouse(mouseButton, action, x, y, motionListener.inRelativeMode());
1777  } else {
1778  switch(action) {
1779  case MotionEvent.ACTION_MOVE:
1780  for (i = 0; i < pointerCount; i++) {
1781  pointerFingerId = event.getPointerId(i);
1782  x = event.getX(i) / mWidth;
1783  y = event.getY(i) / mHeight;
1784  p = event.getPressure(i);
1785  if (p > 1.0f) {
1786  // may be larger than 1.0f on some devices
1787  // see the documentation of getPressure(i)
1788  p = 1.0f;
1789  }
1790  SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
1791  }
1792  break;
1793 
1794  case MotionEvent.ACTION_UP:
1795  case MotionEvent.ACTION_DOWN:
1796  // Primary pointer up/down, the index is always zero
1797  i = 0;
1798  case MotionEvent.ACTION_POINTER_UP:
1799  case MotionEvent.ACTION_POINTER_DOWN:
1800  // Non primary pointer up/down
1801  if (i == -1) {
1802  i = event.getActionIndex();
1803  }
1804 
1805  pointerFingerId = event.getPointerId(i);
1806  x = event.getX(i) / mWidth;
1807  y = event.getY(i) / mHeight;
1808  p = event.getPressure(i);
1809  if (p > 1.0f) {
1810  // may be larger than 1.0f on some devices
1811  // see the documentation of getPressure(i)
1812  p = 1.0f;
1813  }
1814  SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
1815  break;
1816 
1817  case MotionEvent.ACTION_CANCEL:
1818  for (i = 0; i < pointerCount; i++) {
1819  pointerFingerId = event.getPointerId(i);
1820  x = event.getX(i) / mWidth;
1821  y = event.getY(i) / mHeight;
1822  p = event.getPressure(i);
1823  if (p > 1.0f) {
1824  // may be larger than 1.0f on some devices
1825  // see the documentation of getPressure(i)
1826  p = 1.0f;
1827  }
1828  SDLActivity.onNativeTouch(touchDevId, pointerFingerId, MotionEvent.ACTION_UP, x, y, p);
1829  }
1830  break;
1831 
1832  default:
1833  break;
1834  }
1835  }
1836 
1837  return true;
1838  }
1839 
1840  // Sensor events
1841  public void enableSensor(int sensortype, boolean enabled) {
1842  // TODO: This uses getDefaultSensor - what if we have >1 accels?
1843  if (enabled) {
1844  mSensorManager.registerListener(this,
1845  mSensorManager.getDefaultSensor(sensortype),
1846  SensorManager.SENSOR_DELAY_GAME, null);
1847  } else {
1848  mSensorManager.unregisterListener(this,
1849  mSensorManager.getDefaultSensor(sensortype));
1850  }
1851  }
1852 
1853  @Override
1854  public void onAccuracyChanged(Sensor sensor, int accuracy) {
1855  // TODO
1856  }
1857 
1858  @Override
1859  public void onSensorChanged(SensorEvent event) {
1860  if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
1861 
1862  // Since we may have an orientation set, we won't receive onConfigurationChanged events.
1863  // We thus should check here.
1864  int newOrientation = SDLActivity.SDL_ORIENTATION_UNKNOWN;
1865 
1866  float x, y;
1867  switch (mDisplay.getRotation()) {
1868  case Surface.ROTATION_90:
1869  x = -event.values[1];
1870  y = event.values[0];
1871  newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE;
1872  break;
1873  case Surface.ROTATION_270:
1874  x = event.values[1];
1875  y = -event.values[0];
1877  break;
1878  case Surface.ROTATION_180:
1879  x = -event.values[1];
1880  y = -event.values[0];
1882  break;
1883  default:
1884  x = event.values[0];
1885  y = event.values[1];
1886  newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT;
1887  break;
1888  }
1889 
1890  if (newOrientation != SDLActivity.mCurrentOrientation) {
1891  SDLActivity.mCurrentOrientation = newOrientation;
1892  SDLActivity.onNativeOrientationChanged(newOrientation);
1893  }
1894 
1895  SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH,
1896  y / SensorManager.GRAVITY_EARTH,
1897  event.values[2] / SensorManager.GRAVITY_EARTH);
1898 
1899 
1900  }
1901  }
1902 
1903  // Captured pointer events for API 26.
1904  public boolean onCapturedPointerEvent(MotionEvent event)
1905  {
1906  int action = event.getActionMasked();
1907 
1908  float x, y;
1909  switch (action) {
1910  case MotionEvent.ACTION_SCROLL:
1911  x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
1912  y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
1913  SDLActivity.onNativeMouse(0, action, x, y, false);
1914  return true;
1915 
1916  case MotionEvent.ACTION_HOVER_MOVE:
1917  case MotionEvent.ACTION_MOVE:
1918  x = event.getX(0);
1919  y = event.getY(0);
1920  SDLActivity.onNativeMouse(0, action, x, y, true);
1921  return true;
1922 
1923  case MotionEvent.ACTION_BUTTON_PRESS:
1924  case MotionEvent.ACTION_BUTTON_RELEASE:
1925 
1926  // Change our action value to what SDL's code expects.
1927  if (action == MotionEvent.ACTION_BUTTON_PRESS) {
1928  action = MotionEvent.ACTION_DOWN;
1929  }
1930  else if (action == MotionEvent.ACTION_BUTTON_RELEASE) {
1931  action = MotionEvent.ACTION_UP;
1932  }
1933 
1934  x = event.getX(0);
1935  y = event.getY(0);
1936  int button = event.getButtonState();
1937 
1938  SDLActivity.onNativeMouse(button, action, x, y, true);
1939  return true;
1940  }
1941 
1942  return false;
1943  }
1944 
1945 }
1946 
1947 /* This is a fake invisible editor view that receives the input and defines the
1948  * pan&scan region
1949  */
1950 class DummyEdit extends View implements View.OnKeyListener {
1951  InputConnection ic;
1952 
1953  public DummyEdit(Context context) {
1954  super(context);
1955  setFocusableInTouchMode(true);
1956  setFocusable(true);
1957  setOnKeyListener(this);
1958  }
1959 
1960  @Override
1961  public boolean onCheckIsTextEditor() {
1962  return true;
1963  }
1964 
1965  @Override
1966  public boolean onKey(View v, int keyCode, KeyEvent event) {
1967  /*
1968  * This handles the hardware keyboard input
1969  */
1970  if (event.getAction() == KeyEvent.ACTION_DOWN) {
1971  if (SDLActivity.isTextInputEvent(event)) {
1972  ic.commitText(String.valueOf((char) event.getUnicodeChar()), 1);
1973  return true;
1974  }
1975  SDLActivity.onNativeKeyDown(keyCode);
1976  return true;
1977  } else if (event.getAction() == KeyEvent.ACTION_UP) {
1978  SDLActivity.onNativeKeyUp(keyCode);
1979  return true;
1980  }
1981  return false;
1982  }
1983 
1984  //
1985  @Override
1986  public boolean onKeyPreIme (int keyCode, KeyEvent event) {
1987  // As seen on StackOverflow: http://stackoverflow.com/questions/7634346/keyboard-hide-event
1988  // FIXME: Discussion at http://bugzilla.libsdl.org/show_bug.cgi?id=1639
1989  // FIXME: This is not a 100% effective solution to the problem of detecting if the keyboard is showing or not
1990  // FIXME: A more effective solution would be to assume our Layout to be RelativeLayout or LinearLayout
1991  // FIXME: And determine the keyboard presence doing this: http://stackoverflow.com/questions/2150078/how-to-check-visibility-of-software-keyboard-in-android
1992  // FIXME: An even more effective way would be if Android provided this out of the box, but where would the fun be in that :)
1993  if (event.getAction()==KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
1994  if (SDLActivity.mTextEdit != null && SDLActivity.mTextEdit.getVisibility() == View.VISIBLE) {
1996  }
1997  }
1998  return super.onKeyPreIme(keyCode, event);
1999  }
2000 
2001  @Override
2002  public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
2003  ic = new SDLInputConnection(this, true);
2004 
2005  outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;
2006  outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI
2007  | EditorInfo.IME_FLAG_NO_FULLSCREEN /* API 11 */;
2008 
2009  return ic;
2010  }
2011 }
2012 
2013 class SDLInputConnection extends BaseInputConnection {
2014 
2015  public SDLInputConnection(View targetView, boolean fullEditor) {
2016  super(targetView, fullEditor);
2017 
2018  }
2019 
2020  @Override
2021  public boolean sendKeyEvent(KeyEvent event) {
2022  /*
2023  * This used to handle the keycodes from soft keyboard (and IME-translated input from hardkeyboard)
2024  * However, as of Ice Cream Sandwich and later, almost all soft keyboard doesn't generate key presses
2025  * and so we need to generate them ourselves in commitText. To avoid duplicates on the handful of keys
2026  * that still do, we empty this out.
2027  */
2028 
2029  /*
2030  * Return DOES still generate a key event, however. So rather than using it as the 'click a button' key
2031  * as we do with physical keyboards, let's just use it to hide the keyboard.
2032  */
2033 
2034  if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) {
2035  String imeHide = SDLActivity.nativeGetHint("SDL_RETURN_KEY_HIDES_IME");
2036  if ((imeHide != null) && imeHide.equals("1")) {
2037  Context c = SDL.getContext();
2038  if (c instanceof SDLActivity) {
2039  SDLActivity activity = (SDLActivity)c;
2040  activity.sendCommand(SDLActivity.COMMAND_TEXTEDIT_HIDE, null);
2041  return true;
2042  }
2043  }
2044  }
2045 
2046 
2047  return super.sendKeyEvent(event);
2048  }
2049 
2050  @Override
2051  public boolean commitText(CharSequence text, int newCursorPosition) {
2052 
2053  for (int i = 0; i < text.length(); i++) {
2054  char c = text.charAt(i);
2055  nativeGenerateScancodeForUnichar(c);
2056  }
2057 
2058  SDLInputConnection.nativeCommitText(text.toString(), newCursorPosition);
2059 
2060  return super.commitText(text, newCursorPosition);
2061  }
2062 
2063  @Override
2064  public boolean setComposingText(CharSequence text, int newCursorPosition) {
2065 
2066  nativeSetComposingText(text.toString(), newCursorPosition);
2067 
2068  return super.setComposingText(text, newCursorPosition);
2069  }
2070 
2071  public static native void nativeCommitText(String text, int newCursorPosition);
2072 
2073  public native void nativeGenerateScancodeForUnichar(char c);
2074 
2075  public native void nativeSetComposingText(String text, int newCursorPosition);
2076 
2077  @Override
2078  public boolean deleteSurroundingText(int beforeLength, int afterLength) {
2079  // Workaround to capture backspace key. Ref: http://stackoverflow.com/questions/14560344/android-backspace-in-webview-baseinputconnection
2080  // and https://bugzilla.libsdl.org/show_bug.cgi?id=2265
2081  if (beforeLength > 0 && afterLength == 0) {
2082  boolean ret = true;
2083  // backspace(s)
2084  while (beforeLength-- > 0) {
2085  boolean ret_key = sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))
2086  && sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));
2087  ret = ret && ret_key;
2088  }
2089  return ret;
2090  }
2091 
2092  return super.deleteSurroundingText(beforeLength, afterLength);
2093  }
2094 }
2095 
2096 interface SDLClipboardHandler {
2097 
2098  public boolean clipboardHasText();
2099  public String clipboardGetText();
2100  public void clipboardSetText(String string);
2101 
2102 }
2103 
2104 
2105 class SDLClipboardHandler_API11 implements
2106  SDLClipboardHandler,
2107  android.content.ClipboardManager.OnPrimaryClipChangedListener {
2108 
2109  protected android.content.ClipboardManager mClipMgr;
2110 
2111  SDLClipboardHandler_API11() {
2112  mClipMgr = (android.content.ClipboardManager) SDL.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
2113  mClipMgr.addPrimaryClipChangedListener(this);
2114  }
2115 
2116  @Override
2117  public boolean clipboardHasText() {
2118  return mClipMgr.hasText();
2119  }
2120 
2121  @Override
2122  public String clipboardGetText() {
2123  CharSequence text;
2124  text = mClipMgr.getText();
2125  if (text != null) {
2126  return text.toString();
2127  }
2128  return null;
2129  }
2130 
2131  @Override
2132  public void clipboardSetText(String string) {
2133  mClipMgr.removePrimaryClipChangedListener(this);
2134  mClipMgr.setText(string);
2135  mClipMgr.addPrimaryClipChangedListener(this);
2136  }
2137 
2138  @Override
2139  public void onPrimaryClipChanged() {
2141  }
2142 
2143 }
2144 
2145 class SDLClipboardHandler_Old implements
2146  SDLClipboardHandler {
2147 
2148  protected android.text.ClipboardManager mClipMgrOld;
2149 
2150  SDLClipboardHandler_Old() {
2151  mClipMgrOld = (android.text.ClipboardManager) SDL.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
2152  }
2153 
2154  @Override
2155  public boolean clipboardHasText() {
2156  return mClipMgrOld.hasText();
2157  }
2158 
2159  @Override
2160  public String clipboardGetText() {
2161  CharSequence text;
2162  text = mClipMgrOld.getText();
2163  if (text != null) {
2164  return text.toString();
2165  }
2166  return null;
2167  }
2168 
2169  @Override
2170  public void clipboardSetText(String string) {
2171  mClipMgrOld.setText(string);
2172  }
2173 }
2174 
GLuint * ids
static boolean isDeXMode()
static boolean isDeviceSDLJoystick(int deviceId)
static native void onNativeKeyboardFocusLost()
static int getCurrentOrientation()
GLsizei GLfixed GLfixed GLfixed GLfixed const GLubyte * bitmap
GLuint id
static boolean mExitCalledFromJava
static SDLGenericMotionListener_API12 mMotionListener
int messageboxShowMessageBox(final int flags, final String title, final String message, final int[] buttonFlags, final int[] buttonIds, final String[] buttonTexts, final int[] colors)
static boolean setActivityTitle(String title)
static final String TAG
GLdouble GLdouble z
static native String nativeGetHint(String name)
static native void onNativeSurfaceDestroyed()
SDL_Texture * button
GLuint64EXT * result
static native void onNativeMouse(int button, int action, float x, float y, boolean relative)
SDL_PRINTF_FORMAT_STRING const char int SDL_PRINTF_FORMAT_STRING const char int SDL_PRINTF_FORMAT_STRING const char int SDL_PRINTF_FORMAT_STRING const char const char SDL_SCANF_FORMAT_STRING const char return SDL_ThreadFunction const char void return Uint32 return Uint32 SDL_AssertionHandler void SDL_SpinLock SDL_atomic_t int int return SDL_atomic_t return void void void return void return int return SDL_AudioSpec SDL_AudioSpec return int int return return int SDL_RWops int SDL_AudioSpec Uint8 Uint32 * e
GLsizei GLenum * sources
const GLdouble * v
Definition: SDL_opengl.h:2064
static boolean setRelativeMouseEnabled(boolean enabled)
static void release(HIDDeviceManager manager)
GLint GLint GLint GLint GLint x
Definition: SDL_opengl.h:1574
GLuint GLsizei const GLchar * message
static void loadLibrary(String libraryName)
Definition: SDL.java:38
static final int SDL_SYSTEM_CURSOR_SIZEWE
static HIDDeviceManager acquire(Context context)
static final int SDL_ORIENTATION_LANDSCAPE
static native void onNativeOrientationChanged(int orientation)
static boolean isTablet()
static int createCustomCursor(int[] colors, int width, int height, int hotSpotX, int hotSpotY)
static final int SDL_SYSTEM_CURSOR_IBEAM
static boolean supportsRelativeMouse()
static final int SDL_SYSTEM_CURSOR_WAIT
static native void onNativeKeyDown(int keycode)
GLfloat GLfloat GLfloat GLfloat h
GLfloat GLfloat p
static native void nativeResume()
static native void onNativeClipboardChanged()
static screen_context_t context
Definition: video.c:25
void onSystemUiVisibilityChange(int visibility)
GLfloat f
GLint GLenum GLsizei GLsizei GLsizei GLint GLsizei const GLvoid * data
Definition: SDL_opengl.h:1974
static boolean setCustomCursor(int cursorID)
static boolean isScreenKeyboardShown()
static Hashtable< Integer, Object > mCursors
static SDLSurface mSurface
static native int onNativePadUp(int device_id, int keycode)
static native int onNativePadDown(int device_id, int keycode)
static void handleNativeState()
GLsizei GLenum const void GLuint GLsizei GLfloat * metrics
static final int SDL_SYSTEM_CURSOR_SIZEALL
GLuint const GLchar * name
static boolean mIsResumedCalled
static NativeState mCurrentNativeState
GLint GLint GLsizei width
Definition: SDL_opengl.h:1572
static void setContext(Context context)
Definition: SDL.java:30
static void handleNativeExit()
static boolean mFullscreenModeActive
static final int COMMAND_USER
static native void onNativeTouch(int touchDevId, int pointerFingerId, int action, float x, float y, float p)
static int [] inputGetInputDeviceIds(int sources)
GLint GLint GLsizei GLsizei GLsizei GLint GLenum format
Definition: SDL_opengl.h:1572
static final int SDL_SYSTEM_CURSOR_NO
boolean dispatchKeyEvent(KeyEvent event)
static SDL_AudioDeviceID device
Definition: loopwave.c:37
static final int SDL_ORIENTATION_PORTRAIT_FLIPPED
static native void nativeQuit()
static InputStream openAPKExpansionInputStream(String fileName)
static final int SDL_SYSTEM_CURSOR_HAND
static boolean mBrokenLibraries
static boolean getManifestEnvironmentVariables()
static void setWindowStyle(boolean fullscreen)
GLuint64 key
Definition: gl2ext.h:2192
SDL_PRINTF_FORMAT_STRING const char int SDL_PRINTF_FORMAT_STRING const char int SDL_PRINTF_FORMAT_STRING const char int SDL_PRINTF_FORMAT_STRING const char const char SDL_SCANF_FORMAT_STRING const char return SDL_ThreadFunction const char void return Uint32 return Uint32 SDL_AssertionHandler void SDL_SpinLock SDL_atomic_t int int return SDL_atomic_t return void void void return void return int return SDL_AudioSpec SDL_AudioSpec return int int return return int SDL_RWops int SDL_AudioSpec Uint8 ** d
static boolean sendMessage(int command, int param)
static void setupJNI()
Definition: SDL.java:14
static void clipboardSetText(String string)
struct _cl_event * event
static final int SDL_ORIENTATION_LANDSCAPE_FLIPPED
static native void onNativeDropFile(String filename)
static boolean isAndroidTV()
static String clipboardGetText()
static boolean mSeparateMouseAndTouch
const GLubyte * c
GLubyte GLubyte GLubyte GLubyte w
GLsizei const GLfloat * value
static void manualBackButton()
static ViewGroup mLayout
static native int nativeSetupJNI()
GLint GLint GLint GLint GLint GLint y
Definition: SDL_opengl.h:1574
void onWindowFocusChanged(boolean hasFocus)
void onCreate(Bundle savedInstanceState)
static boolean setSystemCursor(int cursorID)
static final int SDL_SYSTEM_CURSOR_SIZENESW
static native void nativeLowMemory()
void setOrientationBis(int w, int h, boolean resizable, String hint)
boolean onUnhandledMessage(int command, Object param)
static final int SDL_SYSTEM_CURSOR_SIZENWSE
static boolean isTextInputEvent(KeyEvent event)
static native void onNativeResize(int surfaceWidth, int surfaceHeight, int deviceWidth, int deviceHeight, int format, float rate)
GLenum GLenum GLsizei const GLuint GLboolean enabled
return Display return Display Bool Bool int int int return Display XEvent Bool(*) XPointer return Display return Display Drawable _Xconst char unsigned int unsigned int return Display Pixmap Pixmap XColor XColor unsigned int unsigned int return Display _Xconst char char int char return Display Visual unsigned int int int char unsigned int unsigned int in i)
Definition: SDL_x11sym.h:50
static Method expansionFileMethod
static native void onNativeSurfaceChanged()
static final int SDL_ORIENTATION_UNKNOWN
static native void nativePause()
GLbitfield flags
static char text[MAX_TEXT_LENGTH]
Definition: testime.c:47
static DisplayMetrics getDisplayDPI()
GLint GLint GLsizei GLsizei height
Definition: SDL_opengl.h:1572
static final int SDL_SYSTEM_CURSOR_ARROW
static final int SDL_SYSTEM_CURSOR_SIZENS
static boolean clipboardHasText()
static Context getContext()
Definition: SDL.java:34
EGLSurface EGLNativeWindowType * window
Definition: eglext.h:1025
static HIDDeviceManager mHIDDeviceManager
static boolean showTextInput(int x, int y, int w, int h)
static native void nativeSetenv(String name, String value)
EGLConfig config
Definition: eglext.h:433
Dialog onCreateDialog(int ignore, Bundle args)
const GLfloat * params
static Surface getNativeSurface()
static final int SDL_SYSTEM_CURSOR_CROSSHAIR
GLenum GLenum GLenum GLenum mapping
static SDLActivity mSingleton
static final int SDL_SYSTEM_CURSOR_WAITARROW
static SDLClipboardHandler mClipboardHandler
static NativeState mNextNativeState
static int colors[7]
Definition: testgesture.c:39
GLfloat param
static native int nativeRunMain(String library, String function, Object arguments)
static final int SDL_ORIENTATION_PORTRAIT
static struct @63 buttons[NUM_DRUMS]
static final int SDL_SYSTEM_CURSOR_NONE
static native void onNativeAccel(float x, float y, float z)
static boolean mScreenKeyboardShown
static void initialize()
Definition: SDL.java:21
static boolean isChromebook()
static SDL_Color textColor
Definition: testime.c:46
static Context getContext()
static SDLGenericMotionListener_API12 getMotionListener()
static native void onNativeKeyUp(int keycode)
static void setOrientation(int w, int h, boolean resizable, String hint)