DEADSOFTWARE

Android: added navigation bar hiding hack
[d2df-sdl.git] / android / src / org / libsdl / app / SDLActivity.java
1 package org.libsdl.app;
3 import java.io.IOException;
4 import java.io.InputStream;
5 import java.util.Arrays;
6 import java.lang.reflect.Method;
8 import android.app.*;
9 import android.content.*;
10 import android.text.InputType;
11 import android.view.*;
12 import android.view.inputmethod.BaseInputConnection;
13 import android.view.inputmethod.EditorInfo;
14 import android.view.inputmethod.InputConnection;
15 import android.view.inputmethod.InputMethodManager;
16 import android.widget.RelativeLayout;
17 import android.widget.Button;
18 import android.widget.LinearLayout;
19 import android.widget.TextView;
20 import android.os.*;
21 import android.util.Log;
22 import android.util.SparseArray;
23 import android.graphics.*;
24 import android.graphics.drawable.Drawable;
25 import android.hardware.*;
26 import android.content.pm.ActivityInfo;
28 /**
29 SDL Activity
30 */
31 public class SDLActivity extends Activity {
32 private static final String TAG = "SDL";
34 public static boolean mIsResumedCalled, mIsSurfaceReady, mHasFocus;
36 // Handle the state of the native layer
37 public enum NativeState {
38 INIT, RESUMED, PAUSED
39 }
41 public static NativeState mNextNativeState;
42 public static NativeState mCurrentNativeState;
44 public static boolean mExitCalledFromJava;
46 /** If shared libraries (e.g. SDL or the native application) could not be loaded. */
47 public static boolean mBrokenLibraries;
49 // If we want to separate mouse and touch events.
50 // This is only toggled in native code when a hint is set!
51 public static boolean mSeparateMouseAndTouch;
53 // Main components
54 protected static SDLActivity mSingleton;
55 protected static SDLSurface mSurface;
56 protected static View mTextEdit;
57 protected static boolean mScreenKeyboardShown;
58 protected static ViewGroup mLayout;
59 protected static SDLClipboardHandler mClipboardHandler;
62 // This is what SDL runs in. It invokes SDL_main(), eventually
63 protected static Thread mSDLThread;
65 /**
66 * This method returns the name of the shared object with the application entry point
67 * It can be overridden by derived classes.
68 */
69 protected String getMainSharedObject() {
70 String library;
71 String[] libraries = SDLActivity.mSingleton.getLibraries();
72 if (libraries.length > 0) {
73 library = "lib" + libraries[libraries.length - 1] + ".so";
74 } else {
75 library = "libmain.so";
76 }
77 return library;
78 }
80 /**
81 * This method returns the name of the application entry point
82 * It can be overridden by derived classes.
83 */
84 protected String getMainFunction() {
85 return "SDL_main";
86 }
88 /**
89 * This method is called by SDL before loading the native shared libraries.
90 * It can be overridden to provide names of shared libraries to be loaded.
91 * The default implementation returns the defaults. It never returns null.
92 * An array returned by a new implementation must at least contain "SDL2".
93 * Also keep in mind that the order the libraries are loaded may matter.
94 * @return names of shared libraries to be loaded (e.g. "SDL2", "main").
95 */
96 protected String[] getLibraries() {
97 return new String[] {
98 "SDL2",
99 // "SDL2_image",
100 // "SDL2_mixer",
101 // "SDL2_net",
102 // "SDL2_ttf",
103 "main"
104 };
107 // Load the .so
108 public void loadLibraries() {
109 for (String lib : getLibraries()) {
110 System.loadLibrary(lib);
114 /**
115 * This method is called by SDL before starting the native application thread.
116 * It can be overridden to provide the arguments after the application name.
117 * The default implementation returns an empty array. It never returns null.
118 * @return arguments for the native application.
119 */
120 protected String[] getArguments() {
121 return new String[0];
124 public static void initialize() {
125 // The static nature of the singleton and Android quirkyness force us to initialize everything here
126 // Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values
127 mSingleton = null;
128 mSurface = null;
129 mTextEdit = null;
130 mLayout = null;
131 mClipboardHandler = null;
132 mSDLThread = null;
133 mExitCalledFromJava = false;
134 mBrokenLibraries = false;
135 mIsResumedCalled = false;
136 mIsSurfaceReady = false;
137 mHasFocus = true;
138 mNextNativeState = NativeState.INIT;
139 mCurrentNativeState = NativeState.INIT;
142 // Setup
143 @Override
144 protected void onCreate(Bundle savedInstanceState) {
145 Log.v(TAG, "Device: " + android.os.Build.DEVICE);
146 Log.v(TAG, "Model: " + android.os.Build.MODEL);
147 Log.v(TAG, "onCreate()");
148 super.onCreate(savedInstanceState);
150 // Load shared libraries
151 String errorMsgBrokenLib = "";
152 try {
153 loadLibraries();
154 } catch(UnsatisfiedLinkError e) {
155 System.err.println(e.getMessage());
156 mBrokenLibraries = true;
157 errorMsgBrokenLib = e.getMessage();
158 } catch(Exception e) {
159 System.err.println(e.getMessage());
160 mBrokenLibraries = true;
161 errorMsgBrokenLib = e.getMessage();
164 if (mBrokenLibraries)
166 AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this);
167 dlgAlert.setMessage("An error occurred while trying to start the application. Please try again and/or reinstall."
168 + System.getProperty("line.separator")
169 + System.getProperty("line.separator")
170 + "Error: " + errorMsgBrokenLib);
171 dlgAlert.setTitle("SDL Error");
172 dlgAlert.setPositiveButton("Exit",
173 new DialogInterface.OnClickListener() {
174 @Override
175 public void onClick(DialogInterface dialog,int id) {
176 // if this button is clicked, close current activity
177 SDLActivity.mSingleton.finish();
179 });
180 dlgAlert.setCancelable(false);
181 dlgAlert.create().show();
183 return;
186 // Set up JNI
187 SDL.setupJNI();
189 // Initialize state
190 SDL.initialize();
192 // So we can call stuff from static callbacks
193 mSingleton = this;
194 SDL.setContext(this);
196 if (Build.VERSION.SDK_INT >= 11) {
197 mClipboardHandler = new SDLClipboardHandler_API11();
198 } else {
199 /* Before API 11, no clipboard notification (eg no SDL_CLIPBOARDUPDATE) */
200 mClipboardHandler = new SDLClipboardHandler_Old();
203 // Set up the surface
204 mSurface = new SDLSurface(getApplication());
206 mLayout = new RelativeLayout(this);
207 mLayout.addView(mSurface);
209 setContentView(mLayout);
211 // Get filename from "Open with" of another application
212 Intent intent = getIntent();
213 if (intent != null && intent.getData() != null) {
214 String filename = intent.getData().getPath();
215 if (filename != null) {
216 Log.v(TAG, "Got filename: " + filename);
217 SDLActivity.onNativeDropFile(filename);
221 hideNavigationBar();
224 // Events
225 @Override
226 protected void onPause() {
227 Log.v(TAG, "onPause()");
228 super.onPause();
229 mNextNativeState = NativeState.PAUSED;
230 mIsResumedCalled = false;
232 if (SDLActivity.mBrokenLibraries) {
233 return;
236 SDLActivity.handleNativeState();
239 @Override
240 protected void onResume() {
241 Log.v(TAG, "onResume()");
242 super.onResume();
243 mNextNativeState = NativeState.RESUMED;
244 mIsResumedCalled = true;
246 hideNavigationBar();
248 if (SDLActivity.mBrokenLibraries) {
249 return;
252 SDLActivity.handleNativeState();
256 @Override
257 public void onWindowFocusChanged(boolean hasFocus) {
258 super.onWindowFocusChanged(hasFocus);
259 Log.v(TAG, "onWindowFocusChanged(): " + hasFocus);
261 if (SDLActivity.mBrokenLibraries) {
262 return;
265 SDLActivity.mHasFocus = hasFocus;
266 if (hasFocus) {
267 mNextNativeState = NativeState.RESUMED;
268 } else {
269 mNextNativeState = NativeState.PAUSED;
272 SDLActivity.handleNativeState();
275 @Override
276 public void onLowMemory() {
277 Log.v(TAG, "onLowMemory()");
278 super.onLowMemory();
280 if (SDLActivity.mBrokenLibraries) {
281 return;
284 SDLActivity.nativeLowMemory();
287 @Override
288 protected void onDestroy() {
289 Log.v(TAG, "onDestroy()");
291 if (SDLActivity.mBrokenLibraries) {
292 super.onDestroy();
293 // Reset everything in case the user re opens the app
294 SDLActivity.initialize();
295 return;
298 mNextNativeState = NativeState.PAUSED;
299 SDLActivity.handleNativeState();
301 // Send a quit message to the application
302 SDLActivity.mExitCalledFromJava = true;
303 SDLActivity.nativeQuit();
305 // Now wait for the SDL thread to quit
306 if (SDLActivity.mSDLThread != null) {
307 try {
308 SDLActivity.mSDLThread.join();
309 } catch(Exception e) {
310 Log.v(TAG, "Problem stopping thread: " + e);
312 SDLActivity.mSDLThread = null;
314 //Log.v(TAG, "Finished waiting for SDL thread");
317 super.onDestroy();
319 // Reset everything in case the user re opens the app
320 SDLActivity.initialize();
323 @Override
324 public boolean dispatchKeyEvent(KeyEvent event) {
326 if (SDLActivity.mBrokenLibraries) {
327 return false;
330 int keyCode = event.getKeyCode();
331 // Ignore certain special keys so they're handled by Android
332 if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ||
333 keyCode == KeyEvent.KEYCODE_VOLUME_UP ||
334 keyCode == KeyEvent.KEYCODE_CAMERA ||
335 keyCode == KeyEvent.KEYCODE_ZOOM_IN || /* API 11 */
336 keyCode == KeyEvent.KEYCODE_ZOOM_OUT /* API 11 */
337 ) {
338 return false;
340 return super.dispatchKeyEvent(event);
343 /* Transition to next state */
344 public static void handleNativeState() {
346 if (mNextNativeState == mCurrentNativeState) {
347 // Already in same state, discard.
348 return;
351 // Try a transition to init state
352 if (mNextNativeState == NativeState.INIT) {
354 mCurrentNativeState = mNextNativeState;
355 return;
358 // Try a transition to paused state
359 if (mNextNativeState == NativeState.PAUSED) {
360 nativePause();
361 mSurface.handlePause();
362 mCurrentNativeState = mNextNativeState;
363 return;
366 // Try a transition to resumed state
367 if (mNextNativeState == NativeState.RESUMED) {
368 if (mIsSurfaceReady && mHasFocus && mIsResumedCalled) {
369 if (mSDLThread == null) {
370 // This is the entry point to the C app.
371 // Start up the C app thread and enable sensor input for the first time
372 // FIXME: Why aren't we enabling sensor input at start?
374 final Thread sdlThread = new Thread(new SDLMain(), "SDLThread");
375 mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, true);
376 sdlThread.start();
378 // Set up a listener thread to catch when the native thread ends
379 mSDLThread = new Thread(new Runnable() {
380 @Override
381 public void run() {
382 try {
383 sdlThread.join();
384 } catch (Exception e) {
385 // Ignore any exception
386 } finally {
387 // Native thread has finished
388 if (!mExitCalledFromJava) {
389 handleNativeExit();
393 }, "SDLThreadListener");
395 mSDLThread.start();
398 nativeResume();
399 mSurface.handleResume();
400 mCurrentNativeState = mNextNativeState;
405 /* The native thread has finished */
406 public static void handleNativeExit() {
407 SDLActivity.mSDLThread = null;
408 mSingleton.finish();
412 // Messages from the SDLMain thread
413 static final int COMMAND_CHANGE_TITLE = 1;
414 static final int COMMAND_UNUSED = 2;
415 static final int COMMAND_TEXTEDIT_HIDE = 3;
416 static final int COMMAND_SET_KEEP_SCREEN_ON = 5;
418 protected static final int COMMAND_USER = 0x8000;
420 /**
421 * This method is called by SDL if SDL did not handle a message itself.
422 * This happens if a received message contains an unsupported command.
423 * Method can be overwritten to handle Messages in a different class.
424 * @param command the command of the message.
425 * @param param the parameter of the message. May be null.
426 * @return if the message was handled in overridden method.
427 */
428 protected boolean onUnhandledMessage(int command, Object param) {
429 return false;
432 /**
433 * A Handler class for Messages from native SDL applications.
434 * It uses current Activities as target (e.g. for the title).
435 * static to prevent implicit references to enclosing object.
436 */
437 protected static class SDLCommandHandler extends Handler {
438 @Override
439 public void handleMessage(Message msg) {
440 Context context = SDL.getContext();
441 if (context == null) {
442 Log.e(TAG, "error handling message, getContext() returned null");
443 return;
445 switch (msg.arg1) {
446 case COMMAND_CHANGE_TITLE:
447 if (context instanceof Activity) {
448 ((Activity) context).setTitle((String)msg.obj);
449 } else {
450 Log.e(TAG, "error handling message, getContext() returned no Activity");
452 break;
453 case COMMAND_TEXTEDIT_HIDE:
454 if (mTextEdit != null) {
455 // Note: On some devices setting view to GONE creates a flicker in landscape.
456 // Setting the View's sizes to 0 is similar to GONE but without the flicker.
457 // The sizes will be set to useful values when the keyboard is shown again.
458 mTextEdit.setLayoutParams(new RelativeLayout.LayoutParams(0, 0));
460 InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
461 imm.hideSoftInputFromWindow(mTextEdit.getWindowToken(), 0);
463 hideNavigationBar();
465 mScreenKeyboardShown = false;
467 break;
468 case COMMAND_SET_KEEP_SCREEN_ON:
470 if (context instanceof Activity) {
471 Window window = ((Activity) context).getWindow();
472 if (window != null) {
473 if ((msg.obj instanceof Integer) && (((Integer) msg.obj).intValue() != 0)) {
474 window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
475 } else {
476 window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
480 break;
482 default:
483 if ((context instanceof SDLActivity) && !((SDLActivity) context).onUnhandledMessage(msg.arg1, msg.obj)) {
484 Log.e(TAG, "error handling message, command is " + msg.arg1);
490 // Handler for the messages
491 Handler commandHandler = new SDLCommandHandler();
493 // Send a message from the SDLMain thread
494 boolean sendCommand(int command, Object data) {
495 Message msg = commandHandler.obtainMessage();
496 msg.arg1 = command;
497 msg.obj = data;
498 return commandHandler.sendMessage(msg);
501 // C functions we call
502 public static native int nativeSetupJNI();
503 public static native int nativeRunMain(String library, String function, Object arguments);
504 public static native void nativeLowMemory();
505 public static native void nativeQuit();
506 public static native void nativePause();
507 public static native void nativeResume();
508 public static native void onNativeDropFile(String filename);
509 public static native void onNativeResize(int x, int y, int format, float rate);
510 public static native void onNativeKeyDown(int keycode);
511 public static native void onNativeKeyUp(int keycode);
512 public static native void onNativeKeyboardFocusLost();
513 public static native void onNativeMouse(int button, int action, float x, float y);
514 public static native void onNativeTouch(int touchDevId, int pointerFingerId,
515 int action, float x,
516 float y, float p);
517 public static native void onNativeAccel(float x, float y, float z);
518 public static native void onNativeClipboardChanged();
519 public static native void onNativeSurfaceChanged();
520 public static native void onNativeSurfaceDestroyed();
521 public static native String nativeGetHint(String name);
523 /**
524 * This method is called by SDL using JNI.
525 */
526 public static boolean setActivityTitle(String title) {
527 // Called from SDLMain() thread and can't directly affect the view
528 return mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title);
531 /**
532 * This method is called by SDL using JNI.
533 * This is a static method for JNI convenience, it calls a non-static method
534 * so that is can be overridden
535 */
536 public static void setOrientation(int w, int h, boolean resizable, String hint)
538 if (mSingleton != null) {
539 mSingleton.setOrientationBis(w, h, resizable, hint);
543 /**
544 * This can be overridden
545 */
546 public void setOrientationBis(int w, int h, boolean resizable, String hint)
548 int orientation = -1;
550 if (hint != null && !hint.equals("")) {
551 if (hint.contains("LandscapeRight") && hint.contains("LandscapeLeft")) {
552 orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
553 } else if (hint.contains("LandscapeRight")) {
554 orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
555 } else if (hint.contains("LandscapeLeft")) {
556 orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
557 } else if (hint.contains("Portrait") && hint.contains("PortraitUpsideDown")) {
558 orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT;
559 } else if (hint.contains("Portrait")) {
560 orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
561 } else if (hint.contains("PortraitUpsideDown")) {
562 orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
566 /* no valid hint */
567 if (orientation == -1) {
568 if (resizable) {
569 /* no fixed orientation */
570 } else {
571 if (w > h) {
572 orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
573 } else {
574 orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT;
579 Log.v("SDL", "setOrientation() orientation=" + orientation + " width=" + w +" height="+ h +" resizable=" + resizable + " hint=" + hint);
580 if (orientation != -1) {
581 mSingleton.setRequestedOrientation(orientation);
586 /**
587 * This method is called by SDL using JNI.
588 */
589 public static boolean isScreenKeyboardShown()
591 if (mTextEdit == null) {
592 return false;
595 if (!mScreenKeyboardShown) {
596 return false;
599 InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
600 return imm.isAcceptingText();
604 /**
605 * This method is called by SDL using JNI.
606 */
607 public static boolean sendMessage(int command, int param) {
608 if (mSingleton == null) {
609 return false;
611 return mSingleton.sendCommand(command, Integer.valueOf(param));
614 /**
615 * This method is called by SDL using JNI.
616 */
617 public static Context getContext() {
618 return SDL.getContext();
621 static class ShowTextInputTask implements Runnable {
622 /*
623 * This is used to regulate the pan&scan method to have some offset from
624 * the bottom edge of the input region and the top edge of an input
625 * method (soft keyboard)
626 */
627 static final int HEIGHT_PADDING = 15;
629 public int x, y, w, h;
631 public ShowTextInputTask(int x, int y, int w, int h) {
632 this.x = x;
633 this.y = y;
634 this.w = w;
635 this.h = h;
638 @Override
639 public void run() {
640 RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(w, h + HEIGHT_PADDING);
641 params.leftMargin = x;
642 params.topMargin = y;
644 if (mTextEdit == null) {
645 mTextEdit = new DummyEdit(SDL.getContext());
647 mLayout.addView(mTextEdit, params);
648 } else {
649 mTextEdit.setLayoutParams(params);
652 mTextEdit.setVisibility(View.VISIBLE);
653 mTextEdit.requestFocus();
655 InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
656 imm.showSoftInput(mTextEdit, 0);
658 mScreenKeyboardShown = true;
662 /**
663 * This method is called by SDL using JNI.
664 */
665 public static boolean showTextInput(int x, int y, int w, int h) {
666 // Transfer the task to the main thread as a Runnable
667 return mSingleton.commandHandler.post(new ShowTextInputTask(x, y, w, h));
670 public static boolean isTextInputEvent(KeyEvent event) {
672 // Key pressed with Ctrl should be sent as SDL_KEYDOWN/SDL_KEYUP and not SDL_TEXTINPUT
673 if (android.os.Build.VERSION.SDK_INT >= 11) {
674 if (event.isCtrlPressed()) {
675 return false;
676 }
679 return event.isPrintingKey() || event.getKeyCode() == KeyEvent.KEYCODE_SPACE;
682 /**
683 * This method is called by SDL using JNI.
684 */
685 public static Surface getNativeSurface() {
686 if (SDLActivity.mSurface == null) {
687 return null;
689 return SDLActivity.mSurface.getNativeSurface();
692 // Input
694 /**
695 * This method is called by SDL using JNI.
696 * @return an array which may be empty but is never null.
697 */
698 public static int[] inputGetInputDeviceIds(int sources) {
699 int[] ids = InputDevice.getDeviceIds();
700 int[] filtered = new int[ids.length];
701 int used = 0;
702 for (int i = 0; i < ids.length; ++i) {
703 InputDevice device = InputDevice.getDevice(ids[i]);
704 if ((device != null) && ((device.getSources() & sources) != 0)) {
705 filtered[used++] = device.getId();
708 return Arrays.copyOf(filtered, used);
711 // APK expansion files support
713 /** com.android.vending.expansion.zipfile.ZipResourceFile object or null. */
714 private static Object expansionFile;
716 /** com.android.vending.expansion.zipfile.ZipResourceFile's getInputStream() or null. */
717 private static Method expansionFileMethod;
719 /**
720 * This method is called by SDL using JNI.
721 * @return an InputStream on success or null if no expansion file was used.
722 * @throws IOException on errors. Message is set for the SDL error message.
723 */
724 public static InputStream openAPKExpansionInputStream(String fileName) throws IOException {
725 // Get a ZipResourceFile representing a merger of both the main and patch files
726 if (expansionFile == null) {
727 String mainHint = nativeGetHint("SDL_ANDROID_APK_EXPANSION_MAIN_FILE_VERSION");
728 if (mainHint == null) {
729 return null; // no expansion use if no main version was set
731 String patchHint = nativeGetHint("SDL_ANDROID_APK_EXPANSION_PATCH_FILE_VERSION");
732 if (patchHint == null) {
733 return null; // no expansion use if no patch version was set
736 Integer mainVersion;
737 Integer patchVersion;
738 try {
739 mainVersion = Integer.valueOf(mainHint);
740 patchVersion = Integer.valueOf(patchHint);
741 } catch (NumberFormatException ex) {
742 ex.printStackTrace();
743 throw new IOException("No valid file versions set for APK expansion files", ex);
746 try {
747 // To avoid direct dependency on Google APK expansion library that is
748 // not a part of Android SDK we access it using reflection
749 expansionFile = Class.forName("com.android.vending.expansion.zipfile.APKExpansionSupport")
750 .getMethod("getAPKExpansionZipFile", Context.class, int.class, int.class)
751 .invoke(null, SDL.getContext(), mainVersion, patchVersion);
753 expansionFileMethod = expansionFile.getClass()
754 .getMethod("getInputStream", String.class);
755 } catch (Exception ex) {
756 ex.printStackTrace();
757 expansionFile = null;
758 expansionFileMethod = null;
759 throw new IOException("Could not access APK expansion support library", ex);
763 // Get an input stream for a known file inside the expansion file ZIPs
764 InputStream fileStream;
765 try {
766 fileStream = (InputStream)expansionFileMethod.invoke(expansionFile, fileName);
767 } catch (Exception ex) {
768 // calling "getInputStream" failed
769 ex.printStackTrace();
770 throw new IOException("Could not open stream from APK expansion file", ex);
773 if (fileStream == null) {
774 // calling "getInputStream" was successful but null was returned
775 throw new IOException("Could not find path in APK expansion file");
778 return fileStream;
781 // Messagebox
783 /** Result of current messagebox. Also used for blocking the calling thread. */
784 protected final int[] messageboxSelection = new int[1];
786 /** Id of current dialog. */
787 protected int dialogs = 0;
789 /**
790 * This method is called by SDL using JNI.
791 * Shows the messagebox from UI thread and block calling thread.
792 * buttonFlags, buttonIds and buttonTexts must have same length.
793 * @param buttonFlags array containing flags for every button.
794 * @param buttonIds array containing id for every button.
795 * @param buttonTexts array containing text for every button.
796 * @param colors null for default or array of length 5 containing colors.
797 * @return button id or -1.
798 */
799 public int messageboxShowMessageBox(
800 final int flags,
801 final String title,
802 final String message,
803 final int[] buttonFlags,
804 final int[] buttonIds,
805 final String[] buttonTexts,
806 final int[] colors) {
808 messageboxSelection[0] = -1;
810 // sanity checks
812 if ((buttonFlags.length != buttonIds.length) && (buttonIds.length != buttonTexts.length)) {
813 return -1; // implementation broken
816 // collect arguments for Dialog
818 final Bundle args = new Bundle();
819 args.putInt("flags", flags);
820 args.putString("title", title);
821 args.putString("message", message);
822 args.putIntArray("buttonFlags", buttonFlags);
823 args.putIntArray("buttonIds", buttonIds);
824 args.putStringArray("buttonTexts", buttonTexts);
825 args.putIntArray("colors", colors);
827 // trigger Dialog creation on UI thread
829 runOnUiThread(new Runnable() {
830 @Override
831 public void run() {
832 showDialog(dialogs++, args);
834 });
836 // block the calling thread
838 synchronized (messageboxSelection) {
839 try {
840 messageboxSelection.wait();
841 } catch (InterruptedException ex) {
842 ex.printStackTrace();
843 return -1;
847 // return selected value
849 return messageboxSelection[0];
852 public static void hideNavigationBar() {
853 if (Build.VERSION.SDK_INT >= 19) {
854 int opt = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
855 | View.SYSTEM_UI_FLAG_FULLSCREEN
856 | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
857 mSingleton.getWindow().getDecorView().setSystemUiVisibility(opt);
861 @Override
862 protected Dialog onCreateDialog(int ignore, Bundle args) {
864 // TODO set values from "flags" to messagebox dialog
866 // get colors
868 int[] colors = args.getIntArray("colors");
869 int backgroundColor;
870 int textColor;
871 int buttonBorderColor;
872 int buttonBackgroundColor;
873 int buttonSelectedColor;
874 if (colors != null) {
875 int i = -1;
876 backgroundColor = colors[++i];
877 textColor = colors[++i];
878 buttonBorderColor = colors[++i];
879 buttonBackgroundColor = colors[++i];
880 buttonSelectedColor = colors[++i];
881 } else {
882 backgroundColor = Color.TRANSPARENT;
883 textColor = Color.TRANSPARENT;
884 buttonBorderColor = Color.TRANSPARENT;
885 buttonBackgroundColor = Color.TRANSPARENT;
886 buttonSelectedColor = Color.TRANSPARENT;
889 // create dialog with title and a listener to wake up calling thread
891 final Dialog dialog = new Dialog(this);
892 dialog.setTitle(args.getString("title"));
893 dialog.setCancelable(false);
894 dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
895 @Override
896 public void onDismiss(DialogInterface unused) {
897 synchronized (messageboxSelection) {
898 messageboxSelection.notify();
901 });
903 // create text
905 TextView message = new TextView(this);
906 message.setGravity(Gravity.CENTER);
907 message.setText(args.getString("message"));
908 if (textColor != Color.TRANSPARENT) {
909 message.setTextColor(textColor);
912 // create buttons
914 int[] buttonFlags = args.getIntArray("buttonFlags");
915 int[] buttonIds = args.getIntArray("buttonIds");
916 String[] buttonTexts = args.getStringArray("buttonTexts");
918 final SparseArray<Button> mapping = new SparseArray<Button>();
920 LinearLayout buttons = new LinearLayout(this);
921 buttons.setOrientation(LinearLayout.HORIZONTAL);
922 buttons.setGravity(Gravity.CENTER);
923 for (int i = 0; i < buttonTexts.length; ++i) {
924 Button button = new Button(this);
925 final int id = buttonIds[i];
926 button.setOnClickListener(new View.OnClickListener() {
927 @Override
928 public void onClick(View v) {
929 messageboxSelection[0] = id;
930 dialog.dismiss();
932 });
933 if (buttonFlags[i] != 0) {
934 // see SDL_messagebox.h
935 if ((buttonFlags[i] & 0x00000001) != 0) {
936 mapping.put(KeyEvent.KEYCODE_ENTER, button);
938 if ((buttonFlags[i] & 0x00000002) != 0) {
939 mapping.put(KeyEvent.KEYCODE_ESCAPE, button); /* API 11 */
942 button.setText(buttonTexts[i]);
943 if (textColor != Color.TRANSPARENT) {
944 button.setTextColor(textColor);
946 if (buttonBorderColor != Color.TRANSPARENT) {
947 // TODO set color for border of messagebox button
949 if (buttonBackgroundColor != Color.TRANSPARENT) {
950 Drawable drawable = button.getBackground();
951 if (drawable == null) {
952 // setting the color this way removes the style
953 button.setBackgroundColor(buttonBackgroundColor);
954 } else {
955 // setting the color this way keeps the style (gradient, padding, etc.)
956 drawable.setColorFilter(buttonBackgroundColor, PorterDuff.Mode.MULTIPLY);
959 if (buttonSelectedColor != Color.TRANSPARENT) {
960 // TODO set color for selected messagebox button
962 buttons.addView(button);
965 // create content
967 LinearLayout content = new LinearLayout(this);
968 content.setOrientation(LinearLayout.VERTICAL);
969 content.addView(message);
970 content.addView(buttons);
971 if (backgroundColor != Color.TRANSPARENT) {
972 content.setBackgroundColor(backgroundColor);
975 // add content to dialog and return
977 dialog.setContentView(content);
978 dialog.setOnKeyListener(new Dialog.OnKeyListener() {
979 @Override
980 public boolean onKey(DialogInterface d, int keyCode, KeyEvent event) {
981 Button button = mapping.get(keyCode);
982 if (button != null) {
983 if (event.getAction() == KeyEvent.ACTION_UP) {
984 button.performClick();
986 return true; // also for ignored actions
988 return false;
990 });
992 return dialog;
995 /**
996 * This method is called by SDL using JNI.
997 */
998 public static boolean clipboardHasText() {
999 return mClipboardHandler.clipboardHasText();
1002 /**
1003 * This method is called by SDL using JNI.
1004 */
1005 public static String clipboardGetText() {
1006 return mClipboardHandler.clipboardGetText();
1009 /**
1010 * This method is called by SDL using JNI.
1011 */
1012 public static void clipboardSetText(String string) {
1013 mClipboardHandler.clipboardSetText(string);
1018 /**
1019 Simple runnable to start the SDL application
1020 */
1021 class SDLMain implements Runnable {
1022 @Override
1023 public void run() {
1024 // Runs SDL_main()
1025 String library = SDLActivity.mSingleton.getMainSharedObject();
1026 String function = SDLActivity.mSingleton.getMainFunction();
1027 String[] arguments = SDLActivity.mSingleton.getArguments();
1029 Log.v("SDL", "Running main function " + function + " from library " + library);
1030 SDLActivity.nativeRunMain(library, function, arguments);
1032 Log.v("SDL", "Finished main function");
1037 /**
1038 SDLSurface. This is what we draw on, so we need to know when it's created
1039 in order to do anything useful.
1041 Because of this, that's where we set up the SDL thread
1042 */
1043 class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
1044 View.OnKeyListener, View.OnTouchListener, SensorEventListener {
1046 // Sensors
1047 protected static SensorManager mSensorManager;
1048 protected static Display mDisplay;
1050 // Keep track of the surface size to normalize touch events
1051 protected static float mWidth, mHeight;
1053 // Startup
1054 public SDLSurface(Context context) {
1055 super(context);
1056 getHolder().addCallback(this);
1058 setFocusable(true);
1059 setFocusableInTouchMode(true);
1060 requestFocus();
1061 setOnKeyListener(this);
1062 setOnTouchListener(this);
1064 mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
1065 mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
1067 if (Build.VERSION.SDK_INT >= 12) {
1068 setOnGenericMotionListener(new SDLGenericMotionListener_API12());
1071 // Some arbitrary defaults to avoid a potential division by zero
1072 mWidth = 1.0f;
1073 mHeight = 1.0f;
1076 public void handlePause() {
1077 enableSensor(Sensor.TYPE_ACCELEROMETER, false);
1080 public void handleResume() {
1081 setFocusable(true);
1082 setFocusableInTouchMode(true);
1083 requestFocus();
1084 setOnKeyListener(this);
1085 setOnTouchListener(this);
1086 enableSensor(Sensor.TYPE_ACCELEROMETER, true);
1089 public Surface getNativeSurface() {
1090 return getHolder().getSurface();
1093 // Called when we have a valid drawing surface
1094 @Override
1095 public void surfaceCreated(SurfaceHolder holder) {
1096 Log.v("SDL", "surfaceCreated()");
1097 holder.setType(SurfaceHolder.SURFACE_TYPE_GPU);
1100 // Called when we lose the surface
1101 @Override
1102 public void surfaceDestroyed(SurfaceHolder holder) {
1103 Log.v("SDL", "surfaceDestroyed()");
1105 // Transition to pause, if needed
1106 SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED;
1107 SDLActivity.handleNativeState();
1109 SDLActivity.mIsSurfaceReady = false;
1110 SDLActivity.onNativeSurfaceDestroyed();
1113 // Called when the surface is resized
1114 @Override
1115 public void surfaceChanged(SurfaceHolder holder,
1116 int format, int width, int height) {
1117 Log.v("SDL", "surfaceChanged()");
1119 int sdlFormat = 0x15151002; // SDL_PIXELFORMAT_RGB565 by default
1120 switch (format) {
1121 case PixelFormat.A_8:
1122 Log.v("SDL", "pixel format A_8");
1123 break;
1124 case PixelFormat.LA_88:
1125 Log.v("SDL", "pixel format LA_88");
1126 break;
1127 case PixelFormat.L_8:
1128 Log.v("SDL", "pixel format L_8");
1129 break;
1130 case PixelFormat.RGBA_4444:
1131 Log.v("SDL", "pixel format RGBA_4444");
1132 sdlFormat = 0x15421002; // SDL_PIXELFORMAT_RGBA4444
1133 break;
1134 case PixelFormat.RGBA_5551:
1135 Log.v("SDL", "pixel format RGBA_5551");
1136 sdlFormat = 0x15441002; // SDL_PIXELFORMAT_RGBA5551
1137 break;
1138 case PixelFormat.RGBA_8888:
1139 Log.v("SDL", "pixel format RGBA_8888");
1140 sdlFormat = 0x16462004; // SDL_PIXELFORMAT_RGBA8888
1141 break;
1142 case PixelFormat.RGBX_8888:
1143 Log.v("SDL", "pixel format RGBX_8888");
1144 sdlFormat = 0x16261804; // SDL_PIXELFORMAT_RGBX8888
1145 break;
1146 case PixelFormat.RGB_332:
1147 Log.v("SDL", "pixel format RGB_332");
1148 sdlFormat = 0x14110801; // SDL_PIXELFORMAT_RGB332
1149 break;
1150 case PixelFormat.RGB_565:
1151 Log.v("SDL", "pixel format RGB_565");
1152 sdlFormat = 0x15151002; // SDL_PIXELFORMAT_RGB565
1153 break;
1154 case PixelFormat.RGB_888:
1155 Log.v("SDL", "pixel format RGB_888");
1156 // Not sure this is right, maybe SDL_PIXELFORMAT_RGB24 instead?
1157 sdlFormat = 0x16161804; // SDL_PIXELFORMAT_RGB888
1158 break;
1159 default:
1160 Log.v("SDL", "pixel format unknown " + format);
1161 break;
1164 mWidth = width;
1165 mHeight = height;
1166 SDLActivity.onNativeResize(width, height, sdlFormat, mDisplay.getRefreshRate());
1167 Log.v("SDL", "Window size: " + width + "x" + height);
1170 boolean skip = false;
1171 int requestedOrientation = SDLActivity.mSingleton.getRequestedOrientation();
1173 if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)
1175 // Accept any
1177 else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT)
1179 if (mWidth > mHeight) {
1180 skip = true;
1182 } else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) {
1183 if (mWidth < mHeight) {
1184 skip = true;
1188 // Special Patch for Square Resolution: Black Berry Passport
1189 if (skip) {
1190 double min = Math.min(mWidth, mHeight);
1191 double max = Math.max(mWidth, mHeight);
1193 if (max / min < 1.20) {
1194 Log.v("SDL", "Don't skip on such aspect-ratio. Could be a square resolution.");
1195 skip = false;
1199 if (skip) {
1200 Log.v("SDL", "Skip .. Surface is not ready.");
1201 SDLActivity.mIsSurfaceReady = false;
1202 return;
1205 /* Surface is ready */
1206 SDLActivity.mIsSurfaceReady = true;
1208 /* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */
1209 SDLActivity.onNativeSurfaceChanged();
1211 SDLActivity.handleNativeState();
1214 // Key events
1215 @Override
1216 public boolean onKey(View v, int keyCode, KeyEvent event) {
1217 // Dispatch the different events depending on where they come from
1218 // Some SOURCE_JOYSTICK, SOURCE_DPAD or SOURCE_GAMEPAD are also SOURCE_KEYBOARD
1219 // So, we try to process them as JOYSTICK/DPAD/GAMEPAD events first, if that fails we try them as KEYBOARD
1220 //
1221 // Furthermore, it's possible a game controller has SOURCE_KEYBOARD and
1222 // SOURCE_JOYSTICK, while its key events arrive from the keyboard source
1223 // So, retrieve the device itself and check all of its sources
1224 if (SDLControllerManager.isDeviceSDLJoystick(event.getDeviceId())) {
1225 // Note that we process events with specific key codes here
1226 if (event.getAction() == KeyEvent.ACTION_DOWN) {
1227 if (SDLControllerManager.onNativePadDown(event.getDeviceId(), keyCode) == 0) {
1228 return true;
1230 } else if (event.getAction() == KeyEvent.ACTION_UP) {
1231 if (SDLControllerManager.onNativePadUp(event.getDeviceId(), keyCode) == 0) {
1232 return true;
1237 if ((event.getSource() & InputDevice.SOURCE_KEYBOARD) != 0) {
1238 if (event.getAction() == KeyEvent.ACTION_DOWN) {
1239 //Log.v("SDL", "key down: " + keyCode);
1240 SDLActivity.onNativeKeyDown(keyCode);
1241 return true;
1243 else if (event.getAction() == KeyEvent.ACTION_UP) {
1244 //Log.v("SDL", "key up: " + keyCode);
1245 SDLActivity.onNativeKeyUp(keyCode);
1246 return true;
1250 if ((event.getSource() & InputDevice.SOURCE_MOUSE) != 0) {
1251 // on some devices key events are sent for mouse BUTTON_BACK/FORWARD presses
1252 // they are ignored here because sending them as mouse input to SDL is messy
1253 if ((keyCode == KeyEvent.KEYCODE_BACK) || (keyCode == KeyEvent.KEYCODE_FORWARD)) {
1254 switch (event.getAction()) {
1255 case KeyEvent.ACTION_DOWN:
1256 case KeyEvent.ACTION_UP:
1257 // mark the event as handled or it will be handled by system
1258 // handling KEYCODE_BACK by system will call onBackPressed()
1259 return true;
1264 return false;
1267 // Touch events
1268 @Override
1269 public boolean onTouch(View v, MotionEvent event) {
1270 /* Ref: http://developer.android.com/training/gestures/multi.html */
1271 final int touchDevId = event.getDeviceId();
1272 final int pointerCount = event.getPointerCount();
1273 int action = event.getActionMasked();
1274 int pointerFingerId;
1275 int mouseButton;
1276 int i = -1;
1277 float x,y,p;
1279 // !!! FIXME: dump this SDK check after 2.0.4 ships and require API14.
1280 if (event.getSource() == InputDevice.SOURCE_MOUSE && SDLActivity.mSeparateMouseAndTouch) {
1281 if (Build.VERSION.SDK_INT < 14) {
1282 mouseButton = 1; // all mouse buttons are the left button
1283 } else {
1284 try {
1285 mouseButton = (Integer) event.getClass().getMethod("getButtonState").invoke(event);
1286 } catch(Exception e) {
1287 mouseButton = 1; // oh well.
1290 SDLActivity.onNativeMouse(mouseButton, action, event.getX(0), event.getY(0));
1291 } else {
1292 switch(action) {
1293 case MotionEvent.ACTION_MOVE:
1294 for (i = 0; i < pointerCount; i++) {
1295 pointerFingerId = event.getPointerId(i);
1296 x = event.getX(i) / mWidth;
1297 y = event.getY(i) / mHeight;
1298 p = event.getPressure(i);
1299 if (p > 1.0f) {
1300 // may be larger than 1.0f on some devices
1301 // see the documentation of getPressure(i)
1302 p = 1.0f;
1304 SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
1306 break;
1308 case MotionEvent.ACTION_UP:
1309 case MotionEvent.ACTION_DOWN:
1310 // Primary pointer up/down, the index is always zero
1311 i = 0;
1312 case MotionEvent.ACTION_POINTER_UP:
1313 case MotionEvent.ACTION_POINTER_DOWN:
1314 // Non primary pointer up/down
1315 if (i == -1) {
1316 i = event.getActionIndex();
1319 pointerFingerId = event.getPointerId(i);
1320 x = event.getX(i) / mWidth;
1321 y = event.getY(i) / mHeight;
1322 p = event.getPressure(i);
1323 if (p > 1.0f) {
1324 // may be larger than 1.0f on some devices
1325 // see the documentation of getPressure(i)
1326 p = 1.0f;
1328 SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
1329 break;
1331 case MotionEvent.ACTION_CANCEL:
1332 for (i = 0; i < pointerCount; i++) {
1333 pointerFingerId = event.getPointerId(i);
1334 x = event.getX(i) / mWidth;
1335 y = event.getY(i) / mHeight;
1336 p = event.getPressure(i);
1337 if (p > 1.0f) {
1338 // may be larger than 1.0f on some devices
1339 // see the documentation of getPressure(i)
1340 p = 1.0f;
1342 SDLActivity.onNativeTouch(touchDevId, pointerFingerId, MotionEvent.ACTION_UP, x, y, p);
1344 break;
1346 default:
1347 break;
1351 return true;
1354 // Sensor events
1355 public void enableSensor(int sensortype, boolean enabled) {
1356 // TODO: This uses getDefaultSensor - what if we have >1 accels?
1357 if (enabled) {
1358 mSensorManager.registerListener(this,
1359 mSensorManager.getDefaultSensor(sensortype),
1360 SensorManager.SENSOR_DELAY_GAME, null);
1361 } else {
1362 mSensorManager.unregisterListener(this,
1363 mSensorManager.getDefaultSensor(sensortype));
1367 @Override
1368 public void onAccuracyChanged(Sensor sensor, int accuracy) {
1369 // TODO
1372 @Override
1373 public void onSensorChanged(SensorEvent event) {
1374 if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
1375 float x, y;
1376 switch (mDisplay.getRotation()) {
1377 case Surface.ROTATION_90:
1378 x = -event.values[1];
1379 y = event.values[0];
1380 break;
1381 case Surface.ROTATION_270:
1382 x = event.values[1];
1383 y = -event.values[0];
1384 break;
1385 case Surface.ROTATION_180:
1386 x = -event.values[1];
1387 y = -event.values[0];
1388 break;
1389 default:
1390 x = event.values[0];
1391 y = event.values[1];
1392 break;
1394 SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH,
1395 y / SensorManager.GRAVITY_EARTH,
1396 event.values[2] / SensorManager.GRAVITY_EARTH);
1401 /* This is a fake invisible editor view that receives the input and defines the
1402 * pan&scan region
1403 */
1404 class DummyEdit extends View implements View.OnKeyListener {
1405 InputConnection ic;
1407 public DummyEdit(Context context) {
1408 super(context);
1409 setFocusableInTouchMode(true);
1410 setFocusable(true);
1411 setOnKeyListener(this);
1414 @Override
1415 public boolean onCheckIsTextEditor() {
1416 return true;
1419 @Override
1420 public boolean onKey(View v, int keyCode, KeyEvent event) {
1421 /*
1422 * This handles the hardware keyboard input
1423 */
1424 if (event.getAction() == KeyEvent.ACTION_DOWN) {
1425 if (SDLActivity.isTextInputEvent(event)) {
1426 ic.commitText(String.valueOf((char) event.getUnicodeChar()), 1);
1428 SDLActivity.onNativeKeyDown(keyCode);
1429 return true;
1430 } else if (event.getAction() == KeyEvent.ACTION_UP) {
1431 SDLActivity.onNativeKeyUp(keyCode);
1432 return true;
1434 return false;
1437 //
1438 @Override
1439 public boolean onKeyPreIme (int keyCode, KeyEvent event) {
1440 // As seen on StackOverflow: http://stackoverflow.com/questions/7634346/keyboard-hide-event
1441 // FIXME: Discussion at http://bugzilla.libsdl.org/show_bug.cgi?id=1639
1442 // FIXME: This is not a 100% effective solution to the problem of detecting if the keyboard is showing or not
1443 // FIXME: A more effective solution would be to assume our Layout to be RelativeLayout or LinearLayout
1444 // FIXME: And determine the keyboard presence doing this: http://stackoverflow.com/questions/2150078/how-to-check-visibility-of-software-keyboard-in-android
1445 // FIXME: An even more effective way would be if Android provided this out of the box, but where would the fun be in that :)
1446 if (event.getAction()==KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
1447 if (SDLActivity.mTextEdit != null && SDLActivity.mTextEdit.getVisibility() == View.VISIBLE) {
1448 SDLActivity.onNativeKeyboardFocusLost();
1451 return super.onKeyPreIme(keyCode, event);
1454 @Override
1455 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
1456 ic = new SDLInputConnection(this, true);
1458 outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;
1459 outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI
1460 | EditorInfo.IME_FLAG_NO_FULLSCREEN /* API 11 */;
1462 return ic;
1466 class SDLInputConnection extends BaseInputConnection {
1468 public SDLInputConnection(View targetView, boolean fullEditor) {
1469 super(targetView, fullEditor);
1473 @Override
1474 public boolean sendKeyEvent(KeyEvent event) {
1475 /*
1476 * This handles the keycodes from soft keyboard (and IME-translated input from hardkeyboard)
1477 */
1478 int keyCode = event.getKeyCode();
1479 if (event.getAction() == KeyEvent.ACTION_DOWN) {
1480 if (SDLActivity.isTextInputEvent(event)) {
1481 commitText(String.valueOf((char) event.getUnicodeChar()), 1);
1483 SDLActivity.onNativeKeyDown(keyCode);
1484 return true;
1485 } else if (event.getAction() == KeyEvent.ACTION_UP) {
1486 SDLActivity.onNativeKeyUp(keyCode);
1487 return true;
1489 return super.sendKeyEvent(event);
1492 @Override
1493 public boolean commitText(CharSequence text, int newCursorPosition) {
1495 nativeCommitText(text.toString(), newCursorPosition);
1497 return super.commitText(text, newCursorPosition);
1500 @Override
1501 public boolean setComposingText(CharSequence text, int newCursorPosition) {
1503 nativeSetComposingText(text.toString(), newCursorPosition);
1505 return super.setComposingText(text, newCursorPosition);
1508 public native void nativeCommitText(String text, int newCursorPosition);
1510 public native void nativeSetComposingText(String text, int newCursorPosition);
1512 @Override
1513 public boolean deleteSurroundingText(int beforeLength, int afterLength) {
1514 // Workaround to capture backspace key. Ref: http://stackoverflow.com/questions/14560344/android-backspace-in-webview-baseinputconnection
1515 // and https://bugzilla.libsdl.org/show_bug.cgi?id=2265
1516 if (beforeLength > 0 && afterLength == 0) {
1517 boolean ret = true;
1518 // backspace(s)
1519 while (beforeLength-- > 0) {
1520 boolean ret_key = sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))
1521 && sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));
1522 ret = ret && ret_key;
1524 return ret;
1527 return super.deleteSurroundingText(beforeLength, afterLength);
1531 interface SDLClipboardHandler {
1533 public boolean clipboardHasText();
1534 public String clipboardGetText();
1535 public void clipboardSetText(String string);
1540 class SDLClipboardHandler_API11 implements
1541 SDLClipboardHandler,
1542 android.content.ClipboardManager.OnPrimaryClipChangedListener {
1544 protected android.content.ClipboardManager mClipMgr;
1546 SDLClipboardHandler_API11() {
1547 mClipMgr = (android.content.ClipboardManager) SDL.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
1548 mClipMgr.addPrimaryClipChangedListener(this);
1551 @Override
1552 public boolean clipboardHasText() {
1553 return mClipMgr.hasText();
1556 @Override
1557 public String clipboardGetText() {
1558 CharSequence text;
1559 text = mClipMgr.getText();
1560 if (text != null) {
1561 return text.toString();
1563 return null;
1566 @Override
1567 public void clipboardSetText(String string) {
1568 mClipMgr.removePrimaryClipChangedListener(this);
1569 mClipMgr.setText(string);
1570 mClipMgr.addPrimaryClipChangedListener(this);
1573 @Override
1574 public void onPrimaryClipChanged() {
1575 SDLActivity.onNativeClipboardChanged();
1580 class SDLClipboardHandler_Old implements
1581 SDLClipboardHandler {
1583 protected android.text.ClipboardManager mClipMgrOld;
1585 SDLClipboardHandler_Old() {
1586 mClipMgrOld = (android.text.ClipboardManager) SDL.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
1589 @Override
1590 public boolean clipboardHasText() {
1591 return mClipMgrOld.hasText();
1594 @Override
1595 public String clipboardGetText() {
1596 CharSequence text;
1597 text = mClipMgrOld.getText();
1598 if (text != null) {
1599 return text.toString();
1601 return null;
1604 @Override
1605 public void clipboardSetText(String string) {
1606 mClipMgrOld.setText(string);