DEADSOFTWARE

57f931e356934049995e79404f0537a88dcb0c82
[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);
222 // Events
223 @Override
224 protected void onPause() {
225 Log.v(TAG, "onPause()");
226 super.onPause();
227 mNextNativeState = NativeState.PAUSED;
228 mIsResumedCalled = false;
230 if (SDLActivity.mBrokenLibraries) {
231 return;
234 SDLActivity.handleNativeState();
237 @Override
238 protected void onResume() {
239 Log.v(TAG, "onResume()");
240 super.onResume();
241 mNextNativeState = NativeState.RESUMED;
242 mIsResumedCalled = true;
244 if (SDLActivity.mBrokenLibraries) {
245 return;
248 SDLActivity.handleNativeState();
252 @Override
253 public void onWindowFocusChanged(boolean hasFocus) {
254 super.onWindowFocusChanged(hasFocus);
255 Log.v(TAG, "onWindowFocusChanged(): " + hasFocus);
257 if (SDLActivity.mBrokenLibraries) {
258 return;
261 SDLActivity.mHasFocus = hasFocus;
262 if (hasFocus) {
263 mNextNativeState = NativeState.RESUMED;
264 } else {
265 mNextNativeState = NativeState.PAUSED;
268 SDLActivity.handleNativeState();
271 @Override
272 public void onLowMemory() {
273 Log.v(TAG, "onLowMemory()");
274 super.onLowMemory();
276 if (SDLActivity.mBrokenLibraries) {
277 return;
280 SDLActivity.nativeLowMemory();
283 @Override
284 protected void onDestroy() {
285 Log.v(TAG, "onDestroy()");
287 if (SDLActivity.mBrokenLibraries) {
288 super.onDestroy();
289 // Reset everything in case the user re opens the app
290 SDLActivity.initialize();
291 return;
294 mNextNativeState = NativeState.PAUSED;
295 SDLActivity.handleNativeState();
297 // Send a quit message to the application
298 SDLActivity.mExitCalledFromJava = true;
299 SDLActivity.nativeQuit();
301 // Now wait for the SDL thread to quit
302 if (SDLActivity.mSDLThread != null) {
303 try {
304 SDLActivity.mSDLThread.join();
305 } catch(Exception e) {
306 Log.v(TAG, "Problem stopping thread: " + e);
308 SDLActivity.mSDLThread = null;
310 //Log.v(TAG, "Finished waiting for SDL thread");
313 super.onDestroy();
315 // Reset everything in case the user re opens the app
316 SDLActivity.initialize();
319 @Override
320 public boolean dispatchKeyEvent(KeyEvent event) {
322 if (SDLActivity.mBrokenLibraries) {
323 return false;
326 int keyCode = event.getKeyCode();
327 // Ignore certain special keys so they're handled by Android
328 if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ||
329 keyCode == KeyEvent.KEYCODE_VOLUME_UP ||
330 keyCode == KeyEvent.KEYCODE_CAMERA ||
331 keyCode == KeyEvent.KEYCODE_ZOOM_IN || /* API 11 */
332 keyCode == KeyEvent.KEYCODE_ZOOM_OUT /* API 11 */
333 ) {
334 return false;
336 return super.dispatchKeyEvent(event);
339 /* Transition to next state */
340 public static void handleNativeState() {
342 if (mNextNativeState == mCurrentNativeState) {
343 // Already in same state, discard.
344 return;
347 // Try a transition to init state
348 if (mNextNativeState == NativeState.INIT) {
350 mCurrentNativeState = mNextNativeState;
351 return;
354 // Try a transition to paused state
355 if (mNextNativeState == NativeState.PAUSED) {
356 nativePause();
357 mSurface.handlePause();
358 mCurrentNativeState = mNextNativeState;
359 return;
362 // Try a transition to resumed state
363 if (mNextNativeState == NativeState.RESUMED) {
364 if (mIsSurfaceReady && mHasFocus && mIsResumedCalled) {
365 if (mSDLThread == null) {
366 // This is the entry point to the C app.
367 // Start up the C app thread and enable sensor input for the first time
368 // FIXME: Why aren't we enabling sensor input at start?
370 final Thread sdlThread = new Thread(new SDLMain(), "SDLThread");
371 mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, true);
372 sdlThread.start();
374 // Set up a listener thread to catch when the native thread ends
375 mSDLThread = new Thread(new Runnable() {
376 @Override
377 public void run() {
378 try {
379 sdlThread.join();
380 } catch (Exception e) {
381 // Ignore any exception
382 } finally {
383 // Native thread has finished
384 if (!mExitCalledFromJava) {
385 handleNativeExit();
389 }, "SDLThreadListener");
391 mSDLThread.start();
394 nativeResume();
395 mSurface.handleResume();
396 mCurrentNativeState = mNextNativeState;
401 /* The native thread has finished */
402 public static void handleNativeExit() {
403 SDLActivity.mSDLThread = null;
404 mSingleton.finish();
408 // Messages from the SDLMain thread
409 static final int COMMAND_CHANGE_TITLE = 1;
410 static final int COMMAND_UNUSED = 2;
411 static final int COMMAND_TEXTEDIT_HIDE = 3;
412 static final int COMMAND_SET_KEEP_SCREEN_ON = 5;
414 protected static final int COMMAND_USER = 0x8000;
416 /**
417 * This method is called by SDL if SDL did not handle a message itself.
418 * This happens if a received message contains an unsupported command.
419 * Method can be overwritten to handle Messages in a different class.
420 * @param command the command of the message.
421 * @param param the parameter of the message. May be null.
422 * @return if the message was handled in overridden method.
423 */
424 protected boolean onUnhandledMessage(int command, Object param) {
425 return false;
428 /**
429 * A Handler class for Messages from native SDL applications.
430 * It uses current Activities as target (e.g. for the title).
431 * static to prevent implicit references to enclosing object.
432 */
433 protected static class SDLCommandHandler extends Handler {
434 @Override
435 public void handleMessage(Message msg) {
436 Context context = SDL.getContext();
437 if (context == null) {
438 Log.e(TAG, "error handling message, getContext() returned null");
439 return;
441 switch (msg.arg1) {
442 case COMMAND_CHANGE_TITLE:
443 if (context instanceof Activity) {
444 ((Activity) context).setTitle((String)msg.obj);
445 } else {
446 Log.e(TAG, "error handling message, getContext() returned no Activity");
448 break;
449 case COMMAND_TEXTEDIT_HIDE:
450 if (mTextEdit != null) {
451 // Note: On some devices setting view to GONE creates a flicker in landscape.
452 // Setting the View's sizes to 0 is similar to GONE but without the flicker.
453 // The sizes will be set to useful values when the keyboard is shown again.
454 mTextEdit.setLayoutParams(new RelativeLayout.LayoutParams(0, 0));
456 InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
457 imm.hideSoftInputFromWindow(mTextEdit.getWindowToken(), 0);
459 mScreenKeyboardShown = false;
461 break;
462 case COMMAND_SET_KEEP_SCREEN_ON:
464 if (context instanceof Activity) {
465 Window window = ((Activity) context).getWindow();
466 if (window != null) {
467 if ((msg.obj instanceof Integer) && (((Integer) msg.obj).intValue() != 0)) {
468 window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
469 } else {
470 window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
474 break;
476 default:
477 if ((context instanceof SDLActivity) && !((SDLActivity) context).onUnhandledMessage(msg.arg1, msg.obj)) {
478 Log.e(TAG, "error handling message, command is " + msg.arg1);
484 // Handler for the messages
485 Handler commandHandler = new SDLCommandHandler();
487 // Send a message from the SDLMain thread
488 boolean sendCommand(int command, Object data) {
489 Message msg = commandHandler.obtainMessage();
490 msg.arg1 = command;
491 msg.obj = data;
492 return commandHandler.sendMessage(msg);
495 // C functions we call
496 public static native int nativeSetupJNI();
497 public static native int nativeRunMain(String library, String function, Object arguments);
498 public static native void nativeLowMemory();
499 public static native void nativeQuit();
500 public static native void nativePause();
501 public static native void nativeResume();
502 public static native void onNativeDropFile(String filename);
503 public static native void onNativeResize(int x, int y, int format, float rate);
504 public static native void onNativeKeyDown(int keycode);
505 public static native void onNativeKeyUp(int keycode);
506 public static native void onNativeKeyboardFocusLost();
507 public static native void onNativeMouse(int button, int action, float x, float y);
508 public static native void onNativeTouch(int touchDevId, int pointerFingerId,
509 int action, float x,
510 float y, float p);
511 public static native void onNativeAccel(float x, float y, float z);
512 public static native void onNativeClipboardChanged();
513 public static native void onNativeSurfaceChanged();
514 public static native void onNativeSurfaceDestroyed();
515 public static native String nativeGetHint(String name);
517 /**
518 * This method is called by SDL using JNI.
519 */
520 public static boolean setActivityTitle(String title) {
521 // Called from SDLMain() thread and can't directly affect the view
522 return mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title);
525 /**
526 * This method is called by SDL using JNI.
527 * This is a static method for JNI convenience, it calls a non-static method
528 * so that is can be overridden
529 */
530 public static void setOrientation(int w, int h, boolean resizable, String hint)
532 if (mSingleton != null) {
533 mSingleton.setOrientationBis(w, h, resizable, hint);
537 /**
538 * This can be overridden
539 */
540 public void setOrientationBis(int w, int h, boolean resizable, String hint)
542 int orientation = -1;
544 if (hint != null && !hint.equals("")) {
545 if (hint.contains("LandscapeRight") && hint.contains("LandscapeLeft")) {
546 orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
547 } else if (hint.contains("LandscapeRight")) {
548 orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
549 } else if (hint.contains("LandscapeLeft")) {
550 orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
551 } else if (hint.contains("Portrait") && hint.contains("PortraitUpsideDown")) {
552 orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT;
553 } else if (hint.contains("Portrait")) {
554 orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
555 } else if (hint.contains("PortraitUpsideDown")) {
556 orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
560 /* no valid hint */
561 if (orientation == -1) {
562 if (resizable) {
563 /* no fixed orientation */
564 } else {
565 if (w > h) {
566 orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
567 } else {
568 orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT;
573 Log.v("SDL", "setOrientation() orientation=" + orientation + " width=" + w +" height="+ h +" resizable=" + resizable + " hint=" + hint);
574 if (orientation != -1) {
575 mSingleton.setRequestedOrientation(orientation);
580 /**
581 * This method is called by SDL using JNI.
582 */
583 public static boolean isScreenKeyboardShown()
585 if (mTextEdit == null) {
586 return false;
589 if (!mScreenKeyboardShown) {
590 return false;
593 InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
594 return imm.isAcceptingText();
598 /**
599 * This method is called by SDL using JNI.
600 */
601 public static boolean sendMessage(int command, int param) {
602 if (mSingleton == null) {
603 return false;
605 return mSingleton.sendCommand(command, Integer.valueOf(param));
608 /**
609 * This method is called by SDL using JNI.
610 */
611 public static Context getContext() {
612 return SDL.getContext();
615 static class ShowTextInputTask implements Runnable {
616 /*
617 * This is used to regulate the pan&scan method to have some offset from
618 * the bottom edge of the input region and the top edge of an input
619 * method (soft keyboard)
620 */
621 static final int HEIGHT_PADDING = 15;
623 public int x, y, w, h;
625 public ShowTextInputTask(int x, int y, int w, int h) {
626 this.x = x;
627 this.y = y;
628 this.w = w;
629 this.h = h;
632 @Override
633 public void run() {
634 RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(w, h + HEIGHT_PADDING);
635 params.leftMargin = x;
636 params.topMargin = y;
638 if (mTextEdit == null) {
639 mTextEdit = new DummyEdit(SDL.getContext());
641 mLayout.addView(mTextEdit, params);
642 } else {
643 mTextEdit.setLayoutParams(params);
646 mTextEdit.setVisibility(View.VISIBLE);
647 mTextEdit.requestFocus();
649 InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
650 imm.showSoftInput(mTextEdit, 0);
652 mScreenKeyboardShown = true;
656 /**
657 * This method is called by SDL using JNI.
658 */
659 public static boolean showTextInput(int x, int y, int w, int h) {
660 // Transfer the task to the main thread as a Runnable
661 return mSingleton.commandHandler.post(new ShowTextInputTask(x, y, w, h));
664 public static boolean isTextInputEvent(KeyEvent event) {
666 // Key pressed with Ctrl should be sent as SDL_KEYDOWN/SDL_KEYUP and not SDL_TEXTINPUT
667 if (android.os.Build.VERSION.SDK_INT >= 11) {
668 if (event.isCtrlPressed()) {
669 return false;
670 }
673 return event.isPrintingKey() || event.getKeyCode() == KeyEvent.KEYCODE_SPACE;
676 /**
677 * This method is called by SDL using JNI.
678 */
679 public static Surface getNativeSurface() {
680 if (SDLActivity.mSurface == null) {
681 return null;
683 return SDLActivity.mSurface.getNativeSurface();
686 // Input
688 /**
689 * This method is called by SDL using JNI.
690 * @return an array which may be empty but is never null.
691 */
692 public static int[] inputGetInputDeviceIds(int sources) {
693 int[] ids = InputDevice.getDeviceIds();
694 int[] filtered = new int[ids.length];
695 int used = 0;
696 for (int i = 0; i < ids.length; ++i) {
697 InputDevice device = InputDevice.getDevice(ids[i]);
698 if ((device != null) && ((device.getSources() & sources) != 0)) {
699 filtered[used++] = device.getId();
702 return Arrays.copyOf(filtered, used);
705 // APK expansion files support
707 /** com.android.vending.expansion.zipfile.ZipResourceFile object or null. */
708 private static Object expansionFile;
710 /** com.android.vending.expansion.zipfile.ZipResourceFile's getInputStream() or null. */
711 private static Method expansionFileMethod;
713 /**
714 * This method is called by SDL using JNI.
715 * @return an InputStream on success or null if no expansion file was used.
716 * @throws IOException on errors. Message is set for the SDL error message.
717 */
718 public static InputStream openAPKExpansionInputStream(String fileName) throws IOException {
719 // Get a ZipResourceFile representing a merger of both the main and patch files
720 if (expansionFile == null) {
721 String mainHint = nativeGetHint("SDL_ANDROID_APK_EXPANSION_MAIN_FILE_VERSION");
722 if (mainHint == null) {
723 return null; // no expansion use if no main version was set
725 String patchHint = nativeGetHint("SDL_ANDROID_APK_EXPANSION_PATCH_FILE_VERSION");
726 if (patchHint == null) {
727 return null; // no expansion use if no patch version was set
730 Integer mainVersion;
731 Integer patchVersion;
732 try {
733 mainVersion = Integer.valueOf(mainHint);
734 patchVersion = Integer.valueOf(patchHint);
735 } catch (NumberFormatException ex) {
736 ex.printStackTrace();
737 throw new IOException("No valid file versions set for APK expansion files", ex);
740 try {
741 // To avoid direct dependency on Google APK expansion library that is
742 // not a part of Android SDK we access it using reflection
743 expansionFile = Class.forName("com.android.vending.expansion.zipfile.APKExpansionSupport")
744 .getMethod("getAPKExpansionZipFile", Context.class, int.class, int.class)
745 .invoke(null, SDL.getContext(), mainVersion, patchVersion);
747 expansionFileMethod = expansionFile.getClass()
748 .getMethod("getInputStream", String.class);
749 } catch (Exception ex) {
750 ex.printStackTrace();
751 expansionFile = null;
752 expansionFileMethod = null;
753 throw new IOException("Could not access APK expansion support library", ex);
757 // Get an input stream for a known file inside the expansion file ZIPs
758 InputStream fileStream;
759 try {
760 fileStream = (InputStream)expansionFileMethod.invoke(expansionFile, fileName);
761 } catch (Exception ex) {
762 // calling "getInputStream" failed
763 ex.printStackTrace();
764 throw new IOException("Could not open stream from APK expansion file", ex);
767 if (fileStream == null) {
768 // calling "getInputStream" was successful but null was returned
769 throw new IOException("Could not find path in APK expansion file");
772 return fileStream;
775 // Messagebox
777 /** Result of current messagebox. Also used for blocking the calling thread. */
778 protected final int[] messageboxSelection = new int[1];
780 /** Id of current dialog. */
781 protected int dialogs = 0;
783 /**
784 * This method is called by SDL using JNI.
785 * Shows the messagebox from UI thread and block calling thread.
786 * buttonFlags, buttonIds and buttonTexts must have same length.
787 * @param buttonFlags array containing flags for every button.
788 * @param buttonIds array containing id for every button.
789 * @param buttonTexts array containing text for every button.
790 * @param colors null for default or array of length 5 containing colors.
791 * @return button id or -1.
792 */
793 public int messageboxShowMessageBox(
794 final int flags,
795 final String title,
796 final String message,
797 final int[] buttonFlags,
798 final int[] buttonIds,
799 final String[] buttonTexts,
800 final int[] colors) {
802 messageboxSelection[0] = -1;
804 // sanity checks
806 if ((buttonFlags.length != buttonIds.length) && (buttonIds.length != buttonTexts.length)) {
807 return -1; // implementation broken
810 // collect arguments for Dialog
812 final Bundle args = new Bundle();
813 args.putInt("flags", flags);
814 args.putString("title", title);
815 args.putString("message", message);
816 args.putIntArray("buttonFlags", buttonFlags);
817 args.putIntArray("buttonIds", buttonIds);
818 args.putStringArray("buttonTexts", buttonTexts);
819 args.putIntArray("colors", colors);
821 // trigger Dialog creation on UI thread
823 runOnUiThread(new Runnable() {
824 @Override
825 public void run() {
826 showDialog(dialogs++, args);
828 });
830 // block the calling thread
832 synchronized (messageboxSelection) {
833 try {
834 messageboxSelection.wait();
835 } catch (InterruptedException ex) {
836 ex.printStackTrace();
837 return -1;
841 // return selected value
843 return messageboxSelection[0];
846 @Override
847 protected Dialog onCreateDialog(int ignore, Bundle args) {
849 // TODO set values from "flags" to messagebox dialog
851 // get colors
853 int[] colors = args.getIntArray("colors");
854 int backgroundColor;
855 int textColor;
856 int buttonBorderColor;
857 int buttonBackgroundColor;
858 int buttonSelectedColor;
859 if (colors != null) {
860 int i = -1;
861 backgroundColor = colors[++i];
862 textColor = colors[++i];
863 buttonBorderColor = colors[++i];
864 buttonBackgroundColor = colors[++i];
865 buttonSelectedColor = colors[++i];
866 } else {
867 backgroundColor = Color.TRANSPARENT;
868 textColor = Color.TRANSPARENT;
869 buttonBorderColor = Color.TRANSPARENT;
870 buttonBackgroundColor = Color.TRANSPARENT;
871 buttonSelectedColor = Color.TRANSPARENT;
874 // create dialog with title and a listener to wake up calling thread
876 final Dialog dialog = new Dialog(this);
877 dialog.setTitle(args.getString("title"));
878 dialog.setCancelable(false);
879 dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
880 @Override
881 public void onDismiss(DialogInterface unused) {
882 synchronized (messageboxSelection) {
883 messageboxSelection.notify();
886 });
888 // create text
890 TextView message = new TextView(this);
891 message.setGravity(Gravity.CENTER);
892 message.setText(args.getString("message"));
893 if (textColor != Color.TRANSPARENT) {
894 message.setTextColor(textColor);
897 // create buttons
899 int[] buttonFlags = args.getIntArray("buttonFlags");
900 int[] buttonIds = args.getIntArray("buttonIds");
901 String[] buttonTexts = args.getStringArray("buttonTexts");
903 final SparseArray<Button> mapping = new SparseArray<Button>();
905 LinearLayout buttons = new LinearLayout(this);
906 buttons.setOrientation(LinearLayout.HORIZONTAL);
907 buttons.setGravity(Gravity.CENTER);
908 for (int i = 0; i < buttonTexts.length; ++i) {
909 Button button = new Button(this);
910 final int id = buttonIds[i];
911 button.setOnClickListener(new View.OnClickListener() {
912 @Override
913 public void onClick(View v) {
914 messageboxSelection[0] = id;
915 dialog.dismiss();
917 });
918 if (buttonFlags[i] != 0) {
919 // see SDL_messagebox.h
920 if ((buttonFlags[i] & 0x00000001) != 0) {
921 mapping.put(KeyEvent.KEYCODE_ENTER, button);
923 if ((buttonFlags[i] & 0x00000002) != 0) {
924 mapping.put(KeyEvent.KEYCODE_ESCAPE, button); /* API 11 */
927 button.setText(buttonTexts[i]);
928 if (textColor != Color.TRANSPARENT) {
929 button.setTextColor(textColor);
931 if (buttonBorderColor != Color.TRANSPARENT) {
932 // TODO set color for border of messagebox button
934 if (buttonBackgroundColor != Color.TRANSPARENT) {
935 Drawable drawable = button.getBackground();
936 if (drawable == null) {
937 // setting the color this way removes the style
938 button.setBackgroundColor(buttonBackgroundColor);
939 } else {
940 // setting the color this way keeps the style (gradient, padding, etc.)
941 drawable.setColorFilter(buttonBackgroundColor, PorterDuff.Mode.MULTIPLY);
944 if (buttonSelectedColor != Color.TRANSPARENT) {
945 // TODO set color for selected messagebox button
947 buttons.addView(button);
950 // create content
952 LinearLayout content = new LinearLayout(this);
953 content.setOrientation(LinearLayout.VERTICAL);
954 content.addView(message);
955 content.addView(buttons);
956 if (backgroundColor != Color.TRANSPARENT) {
957 content.setBackgroundColor(backgroundColor);
960 // add content to dialog and return
962 dialog.setContentView(content);
963 dialog.setOnKeyListener(new Dialog.OnKeyListener() {
964 @Override
965 public boolean onKey(DialogInterface d, int keyCode, KeyEvent event) {
966 Button button = mapping.get(keyCode);
967 if (button != null) {
968 if (event.getAction() == KeyEvent.ACTION_UP) {
969 button.performClick();
971 return true; // also for ignored actions
973 return false;
975 });
977 return dialog;
980 /**
981 * This method is called by SDL using JNI.
982 */
983 public static boolean clipboardHasText() {
984 return mClipboardHandler.clipboardHasText();
987 /**
988 * This method is called by SDL using JNI.
989 */
990 public static String clipboardGetText() {
991 return mClipboardHandler.clipboardGetText();
994 /**
995 * This method is called by SDL using JNI.
996 */
997 public static void clipboardSetText(String string) {
998 mClipboardHandler.clipboardSetText(string);
1003 /**
1004 Simple runnable to start the SDL application
1005 */
1006 class SDLMain implements Runnable {
1007 @Override
1008 public void run() {
1009 // Runs SDL_main()
1010 String library = SDLActivity.mSingleton.getMainSharedObject();
1011 String function = SDLActivity.mSingleton.getMainFunction();
1012 String[] arguments = SDLActivity.mSingleton.getArguments();
1014 Log.v("SDL", "Running main function " + function + " from library " + library);
1015 SDLActivity.nativeRunMain(library, function, arguments);
1017 Log.v("SDL", "Finished main function");
1022 /**
1023 SDLSurface. This is what we draw on, so we need to know when it's created
1024 in order to do anything useful.
1026 Because of this, that's where we set up the SDL thread
1027 */
1028 class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
1029 View.OnKeyListener, View.OnTouchListener, SensorEventListener {
1031 // Sensors
1032 protected static SensorManager mSensorManager;
1033 protected static Display mDisplay;
1035 // Keep track of the surface size to normalize touch events
1036 protected static float mWidth, mHeight;
1038 // Startup
1039 public SDLSurface(Context context) {
1040 super(context);
1041 getHolder().addCallback(this);
1043 setFocusable(true);
1044 setFocusableInTouchMode(true);
1045 requestFocus();
1046 setOnKeyListener(this);
1047 setOnTouchListener(this);
1049 mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
1050 mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
1052 if (Build.VERSION.SDK_INT >= 12) {
1053 setOnGenericMotionListener(new SDLGenericMotionListener_API12());
1056 // Some arbitrary defaults to avoid a potential division by zero
1057 mWidth = 1.0f;
1058 mHeight = 1.0f;
1061 public void handlePause() {
1062 enableSensor(Sensor.TYPE_ACCELEROMETER, false);
1065 public void handleResume() {
1066 setFocusable(true);
1067 setFocusableInTouchMode(true);
1068 requestFocus();
1069 setOnKeyListener(this);
1070 setOnTouchListener(this);
1071 enableSensor(Sensor.TYPE_ACCELEROMETER, true);
1074 public Surface getNativeSurface() {
1075 return getHolder().getSurface();
1078 // Called when we have a valid drawing surface
1079 @Override
1080 public void surfaceCreated(SurfaceHolder holder) {
1081 Log.v("SDL", "surfaceCreated()");
1082 holder.setType(SurfaceHolder.SURFACE_TYPE_GPU);
1085 // Called when we lose the surface
1086 @Override
1087 public void surfaceDestroyed(SurfaceHolder holder) {
1088 Log.v("SDL", "surfaceDestroyed()");
1090 // Transition to pause, if needed
1091 SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED;
1092 SDLActivity.handleNativeState();
1094 SDLActivity.mIsSurfaceReady = false;
1095 SDLActivity.onNativeSurfaceDestroyed();
1098 // Called when the surface is resized
1099 @Override
1100 public void surfaceChanged(SurfaceHolder holder,
1101 int format, int width, int height) {
1102 Log.v("SDL", "surfaceChanged()");
1104 int sdlFormat = 0x15151002; // SDL_PIXELFORMAT_RGB565 by default
1105 switch (format) {
1106 case PixelFormat.A_8:
1107 Log.v("SDL", "pixel format A_8");
1108 break;
1109 case PixelFormat.LA_88:
1110 Log.v("SDL", "pixel format LA_88");
1111 break;
1112 case PixelFormat.L_8:
1113 Log.v("SDL", "pixel format L_8");
1114 break;
1115 case PixelFormat.RGBA_4444:
1116 Log.v("SDL", "pixel format RGBA_4444");
1117 sdlFormat = 0x15421002; // SDL_PIXELFORMAT_RGBA4444
1118 break;
1119 case PixelFormat.RGBA_5551:
1120 Log.v("SDL", "pixel format RGBA_5551");
1121 sdlFormat = 0x15441002; // SDL_PIXELFORMAT_RGBA5551
1122 break;
1123 case PixelFormat.RGBA_8888:
1124 Log.v("SDL", "pixel format RGBA_8888");
1125 sdlFormat = 0x16462004; // SDL_PIXELFORMAT_RGBA8888
1126 break;
1127 case PixelFormat.RGBX_8888:
1128 Log.v("SDL", "pixel format RGBX_8888");
1129 sdlFormat = 0x16261804; // SDL_PIXELFORMAT_RGBX8888
1130 break;
1131 case PixelFormat.RGB_332:
1132 Log.v("SDL", "pixel format RGB_332");
1133 sdlFormat = 0x14110801; // SDL_PIXELFORMAT_RGB332
1134 break;
1135 case PixelFormat.RGB_565:
1136 Log.v("SDL", "pixel format RGB_565");
1137 sdlFormat = 0x15151002; // SDL_PIXELFORMAT_RGB565
1138 break;
1139 case PixelFormat.RGB_888:
1140 Log.v("SDL", "pixel format RGB_888");
1141 // Not sure this is right, maybe SDL_PIXELFORMAT_RGB24 instead?
1142 sdlFormat = 0x16161804; // SDL_PIXELFORMAT_RGB888
1143 break;
1144 default:
1145 Log.v("SDL", "pixel format unknown " + format);
1146 break;
1149 mWidth = width;
1150 mHeight = height;
1151 SDLActivity.onNativeResize(width, height, sdlFormat, mDisplay.getRefreshRate());
1152 Log.v("SDL", "Window size: " + width + "x" + height);
1155 boolean skip = false;
1156 int requestedOrientation = SDLActivity.mSingleton.getRequestedOrientation();
1158 if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)
1160 // Accept any
1162 else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT)
1164 if (mWidth > mHeight) {
1165 skip = true;
1167 } else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) {
1168 if (mWidth < mHeight) {
1169 skip = true;
1173 // Special Patch for Square Resolution: Black Berry Passport
1174 if (skip) {
1175 double min = Math.min(mWidth, mHeight);
1176 double max = Math.max(mWidth, mHeight);
1178 if (max / min < 1.20) {
1179 Log.v("SDL", "Don't skip on such aspect-ratio. Could be a square resolution.");
1180 skip = false;
1184 if (skip) {
1185 Log.v("SDL", "Skip .. Surface is not ready.");
1186 SDLActivity.mIsSurfaceReady = false;
1187 return;
1190 /* Surface is ready */
1191 SDLActivity.mIsSurfaceReady = true;
1193 /* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */
1194 SDLActivity.onNativeSurfaceChanged();
1196 SDLActivity.handleNativeState();
1199 // Key events
1200 @Override
1201 public boolean onKey(View v, int keyCode, KeyEvent event) {
1202 // Dispatch the different events depending on where they come from
1203 // Some SOURCE_JOYSTICK, SOURCE_DPAD or SOURCE_GAMEPAD are also SOURCE_KEYBOARD
1204 // So, we try to process them as JOYSTICK/DPAD/GAMEPAD events first, if that fails we try them as KEYBOARD
1205 //
1206 // Furthermore, it's possible a game controller has SOURCE_KEYBOARD and
1207 // SOURCE_JOYSTICK, while its key events arrive from the keyboard source
1208 // So, retrieve the device itself and check all of its sources
1209 if (SDLControllerManager.isDeviceSDLJoystick(event.getDeviceId())) {
1210 // Note that we process events with specific key codes here
1211 if (event.getAction() == KeyEvent.ACTION_DOWN) {
1212 if (SDLControllerManager.onNativePadDown(event.getDeviceId(), keyCode) == 0) {
1213 return true;
1215 } else if (event.getAction() == KeyEvent.ACTION_UP) {
1216 if (SDLControllerManager.onNativePadUp(event.getDeviceId(), keyCode) == 0) {
1217 return true;
1222 if ((event.getSource() & InputDevice.SOURCE_KEYBOARD) != 0) {
1223 if (event.getAction() == KeyEvent.ACTION_DOWN) {
1224 //Log.v("SDL", "key down: " + keyCode);
1225 SDLActivity.onNativeKeyDown(keyCode);
1226 return true;
1228 else if (event.getAction() == KeyEvent.ACTION_UP) {
1229 //Log.v("SDL", "key up: " + keyCode);
1230 SDLActivity.onNativeKeyUp(keyCode);
1231 return true;
1235 if ((event.getSource() & InputDevice.SOURCE_MOUSE) != 0) {
1236 // on some devices key events are sent for mouse BUTTON_BACK/FORWARD presses
1237 // they are ignored here because sending them as mouse input to SDL is messy
1238 if ((keyCode == KeyEvent.KEYCODE_BACK) || (keyCode == KeyEvent.KEYCODE_FORWARD)) {
1239 switch (event.getAction()) {
1240 case KeyEvent.ACTION_DOWN:
1241 case KeyEvent.ACTION_UP:
1242 // mark the event as handled or it will be handled by system
1243 // handling KEYCODE_BACK by system will call onBackPressed()
1244 return true;
1249 return false;
1252 // Touch events
1253 @Override
1254 public boolean onTouch(View v, MotionEvent event) {
1255 /* Ref: http://developer.android.com/training/gestures/multi.html */
1256 final int touchDevId = event.getDeviceId();
1257 final int pointerCount = event.getPointerCount();
1258 int action = event.getActionMasked();
1259 int pointerFingerId;
1260 int mouseButton;
1261 int i = -1;
1262 float x,y,p;
1264 // !!! FIXME: dump this SDK check after 2.0.4 ships and require API14.
1265 if (event.getSource() == InputDevice.SOURCE_MOUSE && SDLActivity.mSeparateMouseAndTouch) {
1266 if (Build.VERSION.SDK_INT < 14) {
1267 mouseButton = 1; // all mouse buttons are the left button
1268 } else {
1269 try {
1270 mouseButton = (Integer) event.getClass().getMethod("getButtonState").invoke(event);
1271 } catch(Exception e) {
1272 mouseButton = 1; // oh well.
1275 SDLActivity.onNativeMouse(mouseButton, action, event.getX(0), event.getY(0));
1276 } else {
1277 switch(action) {
1278 case MotionEvent.ACTION_MOVE:
1279 for (i = 0; i < pointerCount; i++) {
1280 pointerFingerId = event.getPointerId(i);
1281 x = event.getX(i) / mWidth;
1282 y = event.getY(i) / mHeight;
1283 p = event.getPressure(i);
1284 if (p > 1.0f) {
1285 // may be larger than 1.0f on some devices
1286 // see the documentation of getPressure(i)
1287 p = 1.0f;
1289 SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
1291 break;
1293 case MotionEvent.ACTION_UP:
1294 case MotionEvent.ACTION_DOWN:
1295 // Primary pointer up/down, the index is always zero
1296 i = 0;
1297 case MotionEvent.ACTION_POINTER_UP:
1298 case MotionEvent.ACTION_POINTER_DOWN:
1299 // Non primary pointer up/down
1300 if (i == -1) {
1301 i = event.getActionIndex();
1304 pointerFingerId = event.getPointerId(i);
1305 x = event.getX(i) / mWidth;
1306 y = event.getY(i) / mHeight;
1307 p = event.getPressure(i);
1308 if (p > 1.0f) {
1309 // may be larger than 1.0f on some devices
1310 // see the documentation of getPressure(i)
1311 p = 1.0f;
1313 SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
1314 break;
1316 case MotionEvent.ACTION_CANCEL:
1317 for (i = 0; i < pointerCount; i++) {
1318 pointerFingerId = event.getPointerId(i);
1319 x = event.getX(i) / mWidth;
1320 y = event.getY(i) / mHeight;
1321 p = event.getPressure(i);
1322 if (p > 1.0f) {
1323 // may be larger than 1.0f on some devices
1324 // see the documentation of getPressure(i)
1325 p = 1.0f;
1327 SDLActivity.onNativeTouch(touchDevId, pointerFingerId, MotionEvent.ACTION_UP, x, y, p);
1329 break;
1331 default:
1332 break;
1336 return true;
1339 // Sensor events
1340 public void enableSensor(int sensortype, boolean enabled) {
1341 // TODO: This uses getDefaultSensor - what if we have >1 accels?
1342 if (enabled) {
1343 mSensorManager.registerListener(this,
1344 mSensorManager.getDefaultSensor(sensortype),
1345 SensorManager.SENSOR_DELAY_GAME, null);
1346 } else {
1347 mSensorManager.unregisterListener(this,
1348 mSensorManager.getDefaultSensor(sensortype));
1352 @Override
1353 public void onAccuracyChanged(Sensor sensor, int accuracy) {
1354 // TODO
1357 @Override
1358 public void onSensorChanged(SensorEvent event) {
1359 if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
1360 float x, y;
1361 switch (mDisplay.getRotation()) {
1362 case Surface.ROTATION_90:
1363 x = -event.values[1];
1364 y = event.values[0];
1365 break;
1366 case Surface.ROTATION_270:
1367 x = event.values[1];
1368 y = -event.values[0];
1369 break;
1370 case Surface.ROTATION_180:
1371 x = -event.values[1];
1372 y = -event.values[0];
1373 break;
1374 default:
1375 x = event.values[0];
1376 y = event.values[1];
1377 break;
1379 SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH,
1380 y / SensorManager.GRAVITY_EARTH,
1381 event.values[2] / SensorManager.GRAVITY_EARTH);
1386 /* This is a fake invisible editor view that receives the input and defines the
1387 * pan&scan region
1388 */
1389 class DummyEdit extends View implements View.OnKeyListener {
1390 InputConnection ic;
1392 public DummyEdit(Context context) {
1393 super(context);
1394 setFocusableInTouchMode(true);
1395 setFocusable(true);
1396 setOnKeyListener(this);
1399 @Override
1400 public boolean onCheckIsTextEditor() {
1401 return true;
1404 @Override
1405 public boolean onKey(View v, int keyCode, KeyEvent event) {
1406 /*
1407 * This handles the hardware keyboard input
1408 */
1409 if (event.getAction() == KeyEvent.ACTION_DOWN) {
1410 if (SDLActivity.isTextInputEvent(event)) {
1411 ic.commitText(String.valueOf((char) event.getUnicodeChar()), 1);
1413 SDLActivity.onNativeKeyDown(keyCode);
1414 return true;
1415 } else if (event.getAction() == KeyEvent.ACTION_UP) {
1416 SDLActivity.onNativeKeyUp(keyCode);
1417 return true;
1419 return false;
1422 //
1423 @Override
1424 public boolean onKeyPreIme (int keyCode, KeyEvent event) {
1425 // As seen on StackOverflow: http://stackoverflow.com/questions/7634346/keyboard-hide-event
1426 // FIXME: Discussion at http://bugzilla.libsdl.org/show_bug.cgi?id=1639
1427 // FIXME: This is not a 100% effective solution to the problem of detecting if the keyboard is showing or not
1428 // FIXME: A more effective solution would be to assume our Layout to be RelativeLayout or LinearLayout
1429 // FIXME: And determine the keyboard presence doing this: http://stackoverflow.com/questions/2150078/how-to-check-visibility-of-software-keyboard-in-android
1430 // FIXME: An even more effective way would be if Android provided this out of the box, but where would the fun be in that :)
1431 if (event.getAction()==KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
1432 if (SDLActivity.mTextEdit != null && SDLActivity.mTextEdit.getVisibility() == View.VISIBLE) {
1433 SDLActivity.onNativeKeyboardFocusLost();
1436 return super.onKeyPreIme(keyCode, event);
1439 @Override
1440 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
1441 ic = new SDLInputConnection(this, true);
1443 outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;
1444 outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI
1445 | EditorInfo.IME_FLAG_NO_FULLSCREEN /* API 11 */;
1447 return ic;
1451 class SDLInputConnection extends BaseInputConnection {
1453 public SDLInputConnection(View targetView, boolean fullEditor) {
1454 super(targetView, fullEditor);
1458 @Override
1459 public boolean sendKeyEvent(KeyEvent event) {
1460 /*
1461 * This handles the keycodes from soft keyboard (and IME-translated input from hardkeyboard)
1462 */
1463 int keyCode = event.getKeyCode();
1464 if (event.getAction() == KeyEvent.ACTION_DOWN) {
1465 if (SDLActivity.isTextInputEvent(event)) {
1466 commitText(String.valueOf((char) event.getUnicodeChar()), 1);
1468 SDLActivity.onNativeKeyDown(keyCode);
1469 return true;
1470 } else if (event.getAction() == KeyEvent.ACTION_UP) {
1471 SDLActivity.onNativeKeyUp(keyCode);
1472 return true;
1474 return super.sendKeyEvent(event);
1477 @Override
1478 public boolean commitText(CharSequence text, int newCursorPosition) {
1480 nativeCommitText(text.toString(), newCursorPosition);
1482 return super.commitText(text, newCursorPosition);
1485 @Override
1486 public boolean setComposingText(CharSequence text, int newCursorPosition) {
1488 nativeSetComposingText(text.toString(), newCursorPosition);
1490 return super.setComposingText(text, newCursorPosition);
1493 public native void nativeCommitText(String text, int newCursorPosition);
1495 public native void nativeSetComposingText(String text, int newCursorPosition);
1497 @Override
1498 public boolean deleteSurroundingText(int beforeLength, int afterLength) {
1499 // Workaround to capture backspace key. Ref: http://stackoverflow.com/questions/14560344/android-backspace-in-webview-baseinputconnection
1500 // and https://bugzilla.libsdl.org/show_bug.cgi?id=2265
1501 if (beforeLength > 0 && afterLength == 0) {
1502 boolean ret = true;
1503 // backspace(s)
1504 while (beforeLength-- > 0) {
1505 boolean ret_key = sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))
1506 && sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));
1507 ret = ret && ret_key;
1509 return ret;
1512 return super.deleteSurroundingText(beforeLength, afterLength);
1516 interface SDLClipboardHandler {
1518 public boolean clipboardHasText();
1519 public String clipboardGetText();
1520 public void clipboardSetText(String string);
1525 class SDLClipboardHandler_API11 implements
1526 SDLClipboardHandler,
1527 android.content.ClipboardManager.OnPrimaryClipChangedListener {
1529 protected android.content.ClipboardManager mClipMgr;
1531 SDLClipboardHandler_API11() {
1532 mClipMgr = (android.content.ClipboardManager) SDL.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
1533 mClipMgr.addPrimaryClipChangedListener(this);
1536 @Override
1537 public boolean clipboardHasText() {
1538 return mClipMgr.hasText();
1541 @Override
1542 public String clipboardGetText() {
1543 CharSequence text;
1544 text = mClipMgr.getText();
1545 if (text != null) {
1546 return text.toString();
1548 return null;
1551 @Override
1552 public void clipboardSetText(String string) {
1553 mClipMgr.removePrimaryClipChangedListener(this);
1554 mClipMgr.setText(string);
1555 mClipMgr.addPrimaryClipChangedListener(this);
1558 @Override
1559 public void onPrimaryClipChanged() {
1560 SDLActivity.onNativeClipboardChanged();
1565 class SDLClipboardHandler_Old implements
1566 SDLClipboardHandler {
1568 protected android.text.ClipboardManager mClipMgrOld;
1570 SDLClipboardHandler_Old() {
1571 mClipMgrOld = (android.text.ClipboardManager) SDL.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
1574 @Override
1575 public boolean clipboardHasText() {
1576 return mClipMgrOld.hasText();
1579 @Override
1580 public String clipboardGetText() {
1581 CharSequence text;
1582 text = mClipMgrOld.getText();
1583 if (text != null) {
1584 return text.toString();
1586 return null;
1589 @Override
1590 public void clipboardSetText(String string) {
1591 mClipMgrOld.setText(string);