DEADSOFTWARE

9119473f534fd698aa83970175ab9bd58f43cbd1
[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;
7 import java.util.Objects;
9 import android.app.*;
10 import android.content.*;
11 import android.text.InputType;
12 import android.view.*;
13 import android.view.inputmethod.BaseInputConnection;
14 import android.view.inputmethod.EditorInfo;
15 import android.view.inputmethod.InputConnection;
16 import android.view.inputmethod.InputMethodManager;
17 import android.widget.RelativeLayout;
18 import android.widget.Button;
19 import android.widget.LinearLayout;
20 import android.widget.TextView;
21 import android.os.*;
22 import android.util.Log;
23 import android.util.SparseArray;
24 import android.graphics.*;
25 import android.graphics.drawable.Drawable;
26 import android.hardware.*;
27 import android.content.pm.ActivityInfo;
29 /**
30 SDL Activity
31 */
32 public class SDLActivity extends Activity {
33 private static final String TAG = "SDL";
35 public static boolean mIsResumedCalled, mIsSurfaceReady, mHasFocus;
37 // Handle the state of the native layer
38 public enum NativeState {
39 INIT, RESUMED, PAUSED
40 }
42 public static NativeState mNextNativeState;
43 public static NativeState mCurrentNativeState;
45 public static boolean mExitCalledFromJava;
47 /** If shared libraries (e.g. SDL or the native application) could not be loaded. */
48 public static boolean mBrokenLibraries;
50 // If we want to separate mouse and touch events.
51 // This is only toggled in native code when a hint is set!
52 public static boolean mSeparateMouseAndTouch;
54 // Main components
55 protected static SDLActivity mSingleton;
56 protected static SDLSurface mSurface;
57 protected static View mTextEdit;
58 protected static boolean mScreenKeyboardShown;
59 protected static ViewGroup mLayout;
60 protected static SDLClipboardHandler mClipboardHandler;
63 // This is what SDL runs in. It invokes SDL_main(), eventually
64 protected static Thread mSDLThread;
66 /**
67 * This method returns the name of the shared object with the application entry point
68 * It can be overridden by derived classes.
69 */
70 protected String getMainSharedObject() {
71 String library;
72 String[] libraries = SDLActivity.mSingleton.getLibraries();
73 if (libraries.length > 0) {
74 library = "lib" + libraries[libraries.length - 1] + ".so";
75 } else {
76 library = "libmain.so";
77 }
78 return library;
79 }
81 /**
82 * This method returns the name of the application entry point
83 * It can be overridden by derived classes.
84 */
85 protected String getMainFunction() {
86 return "SDL_main";
87 }
89 /**
90 * This method is called by SDL before loading the native shared libraries.
91 * It can be overridden to provide names of shared libraries to be loaded.
92 * The default implementation returns the defaults. It never returns null.
93 * An array returned by a new implementation must at least contain "SDL2".
94 * Also keep in mind that the order the libraries are loaded may matter.
95 * @return names of shared libraries to be loaded (e.g. "SDL2", "main").
96 */
97 protected String[] getLibraries() {
98 return new String[] {
99 "SDL2",
100 // "SDL2_image",
101 // "SDL2_mixer",
102 // "SDL2_net",
103 // "SDL2_ttf",
104 "main"
105 };
108 // Load the .so
109 public void loadLibraries() {
110 for (String lib : getLibraries()) {
111 System.loadLibrary(lib);
115 /**
116 * This method is called by SDL before starting the native application thread.
117 * It can be overridden to provide the arguments after the application name.
118 * The default implementation returns an empty array. It never returns null.
119 * @return arguments for the native application.
120 */
121 protected String[] getArguments() {
122 return new String[0];
125 public static void initialize() {
126 // The static nature of the singleton and Android quirkyness force us to initialize everything here
127 // Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values
128 mSingleton = null;
129 mSurface = null;
130 mTextEdit = null;
131 mLayout = null;
132 mClipboardHandler = null;
133 mSDLThread = null;
134 mExitCalledFromJava = false;
135 mBrokenLibraries = false;
136 mIsResumedCalled = false;
137 mIsSurfaceReady = false;
138 mHasFocus = true;
139 mNextNativeState = NativeState.INIT;
140 mCurrentNativeState = NativeState.INIT;
143 // Setup
144 @Override
145 protected void onCreate(Bundle savedInstanceState) {
146 Log.v(TAG, "Device: " + android.os.Build.DEVICE);
147 Log.v(TAG, "Model: " + android.os.Build.MODEL);
148 Log.v(TAG, "onCreate()");
149 super.onCreate(savedInstanceState);
151 // Load shared libraries
152 String errorMsgBrokenLib = "";
153 try {
154 loadLibraries();
155 } catch(UnsatisfiedLinkError e) {
156 System.err.println(e.getMessage());
157 mBrokenLibraries = true;
158 errorMsgBrokenLib = e.getMessage();
159 } catch(Exception e) {
160 System.err.println(e.getMessage());
161 mBrokenLibraries = true;
162 errorMsgBrokenLib = e.getMessage();
165 if (mBrokenLibraries)
167 AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this);
168 dlgAlert.setMessage("An error occurred while trying to start the application. Please try again and/or reinstall."
169 + System.getProperty("line.separator")
170 + System.getProperty("line.separator")
171 + "Error: " + errorMsgBrokenLib);
172 dlgAlert.setTitle("SDL Error");
173 dlgAlert.setPositiveButton("Exit",
174 new DialogInterface.OnClickListener() {
175 @Override
176 public void onClick(DialogInterface dialog,int id) {
177 // if this button is clicked, close current activity
178 SDLActivity.mSingleton.finish();
180 });
181 dlgAlert.setCancelable(false);
182 dlgAlert.create().show();
184 return;
187 // Set up JNI
188 SDL.setupJNI();
190 // Initialize state
191 SDL.initialize();
193 // So we can call stuff from static callbacks
194 mSingleton = this;
195 SDL.setContext(this);
197 if (Build.VERSION.SDK_INT >= 11) {
198 mClipboardHandler = new SDLClipboardHandler_API11();
199 } else {
200 /* Before API 11, no clipboard notification (eg no SDL_CLIPBOARDUPDATE) */
201 mClipboardHandler = new SDLClipboardHandler_Old();
204 // Set up the surface
205 mSurface = new SDLSurface(getApplication());
207 mLayout = new RelativeLayout(this);
208 mLayout.addView(mSurface);
210 setContentView(mLayout);
212 // Get filename from "Open with" of another application
213 Intent intent = getIntent();
214 if (intent != null && intent.getData() != null) {
215 String filename = intent.getData().getPath();
216 if (filename != null) {
217 Log.v(TAG, "Got filename: " + filename);
218 SDLActivity.onNativeDropFile(filename);
223 // Events
224 @Override
225 protected void onPause() {
226 Log.v(TAG, "onPause()");
227 super.onPause();
228 mNextNativeState = NativeState.PAUSED;
229 mIsResumedCalled = false;
231 if (SDLActivity.mBrokenLibraries) {
232 return;
235 SDLActivity.handleNativeState();
238 @Override
239 protected void onResume() {
240 Log.v(TAG, "onResume()");
241 super.onResume();
242 mNextNativeState = NativeState.RESUMED;
243 mIsResumedCalled = true;
245 if (SDLActivity.mBrokenLibraries) {
246 return;
249 SDLActivity.handleNativeState();
253 @Override
254 public void onWindowFocusChanged(boolean hasFocus) {
255 super.onWindowFocusChanged(hasFocus);
256 Log.v(TAG, "onWindowFocusChanged(): " + hasFocus);
258 if (SDLActivity.mBrokenLibraries) {
259 return;
262 SDLActivity.mHasFocus = hasFocus;
263 if (hasFocus) {
264 mNextNativeState = NativeState.RESUMED;
265 } else {
266 mNextNativeState = NativeState.PAUSED;
269 SDLActivity.handleNativeState();
272 @Override
273 public void onLowMemory() {
274 Log.v(TAG, "onLowMemory()");
275 super.onLowMemory();
277 if (SDLActivity.mBrokenLibraries) {
278 return;
281 SDLActivity.nativeLowMemory();
284 @Override
285 protected void onDestroy() {
286 Log.v(TAG, "onDestroy()");
288 if (SDLActivity.mBrokenLibraries) {
289 super.onDestroy();
290 // Reset everything in case the user re opens the app
291 SDLActivity.initialize();
292 return;
295 mNextNativeState = NativeState.PAUSED;
296 SDLActivity.handleNativeState();
298 // Send a quit message to the application
299 SDLActivity.mExitCalledFromJava = true;
300 SDLActivity.nativeQuit();
302 // Now wait for the SDL thread to quit
303 if (SDLActivity.mSDLThread != null) {
304 try {
305 SDLActivity.mSDLThread.join();
306 } catch(Exception e) {
307 Log.v(TAG, "Problem stopping thread: " + e);
309 SDLActivity.mSDLThread = null;
311 //Log.v(TAG, "Finished waiting for SDL thread");
314 super.onDestroy();
316 // Reset everything in case the user re opens the app
317 SDLActivity.initialize();
320 @Override
321 public boolean dispatchKeyEvent(KeyEvent event) {
323 if (SDLActivity.mBrokenLibraries) {
324 return false;
327 int keyCode = event.getKeyCode();
328 // Ignore certain special keys so they're handled by Android
329 if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ||
330 keyCode == KeyEvent.KEYCODE_VOLUME_UP ||
331 keyCode == KeyEvent.KEYCODE_CAMERA ||
332 keyCode == KeyEvent.KEYCODE_ZOOM_IN || /* API 11 */
333 keyCode == KeyEvent.KEYCODE_ZOOM_OUT /* API 11 */
334 ) {
335 return false;
337 return super.dispatchKeyEvent(event);
340 /* Transition to next state */
341 public static void handleNativeState() {
343 if (mNextNativeState == mCurrentNativeState) {
344 // Already in same state, discard.
345 return;
348 // Try a transition to init state
349 if (mNextNativeState == NativeState.INIT) {
351 mCurrentNativeState = mNextNativeState;
352 return;
355 // Try a transition to paused state
356 if (mNextNativeState == NativeState.PAUSED) {
357 nativePause();
358 mSurface.handlePause();
359 mCurrentNativeState = mNextNativeState;
360 return;
363 // Try a transition to resumed state
364 if (mNextNativeState == NativeState.RESUMED) {
365 if (mIsSurfaceReady && mHasFocus && mIsResumedCalled) {
366 if (mSDLThread == null) {
367 // This is the entry point to the C app.
368 // Start up the C app thread and enable sensor input for the first time
369 // FIXME: Why aren't we enabling sensor input at start?
371 final Thread sdlThread = new Thread(new SDLMain(), "SDLThread");
372 mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, true);
373 sdlThread.start();
375 // Set up a listener thread to catch when the native thread ends
376 mSDLThread = new Thread(new Runnable() {
377 @Override
378 public void run() {
379 try {
380 sdlThread.join();
381 } catch (Exception e) {
382 // Ignore any exception
383 } finally {
384 // Native thread has finished
385 if (!mExitCalledFromJava) {
386 handleNativeExit();
390 }, "SDLThreadListener");
392 mSDLThread.start();
395 nativeResume();
396 mSurface.handleResume();
397 mCurrentNativeState = mNextNativeState;
402 /* The native thread has finished */
403 public static void handleNativeExit() {
404 SDLActivity.mSDLThread = null;
405 mSingleton.finish();
409 // Messages from the SDLMain thread
410 static final int COMMAND_CHANGE_TITLE = 1;
411 static final int COMMAND_UNUSED = 2;
412 static final int COMMAND_TEXTEDIT_HIDE = 3;
413 static final int COMMAND_SET_KEEP_SCREEN_ON = 5;
415 protected static final int COMMAND_USER = 0x8000;
417 /**
418 * This method is called by SDL if SDL did not handle a message itself.
419 * This happens if a received message contains an unsupported command.
420 * Method can be overwritten to handle Messages in a different class.
421 * @param command the command of the message.
422 * @param param the parameter of the message. May be null.
423 * @return if the message was handled in overridden method.
424 */
425 protected boolean onUnhandledMessage(int command, Object param) {
426 return false;
429 /**
430 * A Handler class for Messages from native SDL applications.
431 * It uses current Activities as target (e.g. for the title).
432 * static to prevent implicit references to enclosing object.
433 */
434 protected static class SDLCommandHandler extends Handler {
435 @Override
436 public void handleMessage(Message msg) {
437 Context context = SDL.getContext();
438 if (context == null) {
439 Log.e(TAG, "error handling message, getContext() returned null");
440 return;
442 switch (msg.arg1) {
443 case COMMAND_CHANGE_TITLE:
444 if (context instanceof Activity) {
445 ((Activity) context).setTitle((String)msg.obj);
446 } else {
447 Log.e(TAG, "error handling message, getContext() returned no Activity");
449 break;
450 case COMMAND_TEXTEDIT_HIDE:
451 if (mTextEdit != null) {
452 // Note: On some devices setting view to GONE creates a flicker in landscape.
453 // Setting the View's sizes to 0 is similar to GONE but without the flicker.
454 // The sizes will be set to useful values when the keyboard is shown again.
455 mTextEdit.setLayoutParams(new RelativeLayout.LayoutParams(0, 0));
457 InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
458 imm.hideSoftInputFromWindow(mTextEdit.getWindowToken(), 0);
460 mScreenKeyboardShown = false;
462 break;
463 case COMMAND_SET_KEEP_SCREEN_ON:
465 if (context instanceof Activity) {
466 Window window = ((Activity) context).getWindow();
467 if (window != null) {
468 if ((msg.obj instanceof Integer) && (((Integer) msg.obj).intValue() != 0)) {
469 window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
470 } else {
471 window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
475 break;
477 default:
478 if ((context instanceof SDLActivity) && !((SDLActivity) context).onUnhandledMessage(msg.arg1, msg.obj)) {
479 Log.e(TAG, "error handling message, command is " + msg.arg1);
485 // Handler for the messages
486 Handler commandHandler = new SDLCommandHandler();
488 // Send a message from the SDLMain thread
489 boolean sendCommand(int command, Object data) {
490 Message msg = commandHandler.obtainMessage();
491 msg.arg1 = command;
492 msg.obj = data;
493 return commandHandler.sendMessage(msg);
496 // C functions we call
497 public static native int nativeSetupJNI();
498 public static native int nativeRunMain(String library, String function, Object arguments);
499 public static native void nativeLowMemory();
500 public static native void nativeQuit();
501 public static native void nativePause();
502 public static native void nativeResume();
503 public static native void onNativeDropFile(String filename);
504 public static native void onNativeResize(int x, int y, int format, float rate);
505 public static native void onNativeKeyDown(int keycode);
506 public static native void onNativeKeyUp(int keycode);
507 public static native void onNativeKeyboardFocusLost();
508 public static native void onNativeMouse(int button, int action, float x, float y);
509 public static native void onNativeTouch(int touchDevId, int pointerFingerId,
510 int action, float x,
511 float y, float p);
512 public static native void onNativeAccel(float x, float y, float z);
513 public static native void onNativeClipboardChanged();
514 public static native void onNativeSurfaceChanged();
515 public static native void onNativeSurfaceDestroyed();
516 public static native String nativeGetHint(String name);
518 /**
519 * This method is called by SDL using JNI.
520 */
521 public static boolean setActivityTitle(String title) {
522 // Called from SDLMain() thread and can't directly affect the view
523 return mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title);
526 /**
527 * This method is called by SDL using JNI.
528 * This is a static method for JNI convenience, it calls a non-static method
529 * so that is can be overridden
530 */
531 public static void setOrientation(int w, int h, boolean resizable, String hint)
533 if (mSingleton != null) {
534 mSingleton.setOrientationBis(w, h, resizable, hint);
538 /**
539 * This can be overridden
540 */
541 public void setOrientationBis(int w, int h, boolean resizable, String hint)
543 int orientation = -1;
545 if (!Objects.equals(hint, "")) {
546 if (hint.contains("LandscapeRight") && hint.contains("LandscapeLeft")) {
547 orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
548 } else if (hint.contains("LandscapeRight")) {
549 orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
550 } else if (hint.contains("LandscapeLeft")) {
551 orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
552 } else if (hint.contains("Portrait") && hint.contains("PortraitUpsideDown")) {
553 orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT;
554 } else if (hint.contains("Portrait")) {
555 orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
556 } else if (hint.contains("PortraitUpsideDown")) {
557 orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
561 /* no valid hint */
562 if (orientation == -1) {
563 if (resizable) {
564 /* no fixed orientation */
565 } else {
566 if (w > h) {
567 orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
568 } else {
569 orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT;
574 Log.v("SDL", "setOrientation() orientation=" + orientation + " width=" + w +" height="+ h +" resizable=" + resizable + " hint=" + hint);
575 if (orientation != -1) {
576 mSingleton.setRequestedOrientation(orientation);
581 /**
582 * This method is called by SDL using JNI.
583 */
584 public static boolean isScreenKeyboardShown()
586 if (mTextEdit == null) {
587 return false;
590 if (!mScreenKeyboardShown) {
591 return false;
594 InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
595 return imm.isAcceptingText();
599 /**
600 * This method is called by SDL using JNI.
601 */
602 public static boolean sendMessage(int command, int param) {
603 if (mSingleton == null) {
604 return false;
606 return mSingleton.sendCommand(command, Integer.valueOf(param));
609 /**
610 * This method is called by SDL using JNI.
611 */
612 public static Context getContext() {
613 return SDL.getContext();
616 static class ShowTextInputTask implements Runnable {
617 /*
618 * This is used to regulate the pan&scan method to have some offset from
619 * the bottom edge of the input region and the top edge of an input
620 * method (soft keyboard)
621 */
622 static final int HEIGHT_PADDING = 15;
624 public int x, y, w, h;
626 public ShowTextInputTask(int x, int y, int w, int h) {
627 this.x = x;
628 this.y = y;
629 this.w = w;
630 this.h = h;
633 @Override
634 public void run() {
635 RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(w, h + HEIGHT_PADDING);
636 params.leftMargin = x;
637 params.topMargin = y;
639 if (mTextEdit == null) {
640 mTextEdit = new DummyEdit(SDL.getContext());
642 mLayout.addView(mTextEdit, params);
643 } else {
644 mTextEdit.setLayoutParams(params);
647 mTextEdit.setVisibility(View.VISIBLE);
648 mTextEdit.requestFocus();
650 InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
651 imm.showSoftInput(mTextEdit, 0);
653 mScreenKeyboardShown = true;
657 /**
658 * This method is called by SDL using JNI.
659 */
660 public static boolean showTextInput(int x, int y, int w, int h) {
661 // Transfer the task to the main thread as a Runnable
662 return mSingleton.commandHandler.post(new ShowTextInputTask(x, y, w, h));
665 public static boolean isTextInputEvent(KeyEvent event) {
667 // Key pressed with Ctrl should be sent as SDL_KEYDOWN/SDL_KEYUP and not SDL_TEXTINPUT
668 if (android.os.Build.VERSION.SDK_INT >= 11) {
669 if (event.isCtrlPressed()) {
670 return false;
671 }
674 return event.isPrintingKey() || event.getKeyCode() == KeyEvent.KEYCODE_SPACE;
677 /**
678 * This method is called by SDL using JNI.
679 */
680 public static Surface getNativeSurface() {
681 if (SDLActivity.mSurface == null) {
682 return null;
684 return SDLActivity.mSurface.getNativeSurface();
687 // Input
689 /**
690 * This method is called by SDL using JNI.
691 * @return an array which may be empty but is never null.
692 */
693 public static int[] inputGetInputDeviceIds(int sources) {
694 int[] ids = InputDevice.getDeviceIds();
695 int[] filtered = new int[ids.length];
696 int used = 0;
697 for (int i = 0; i < ids.length; ++i) {
698 InputDevice device = InputDevice.getDevice(ids[i]);
699 if ((device != null) && ((device.getSources() & sources) != 0)) {
700 filtered[used++] = device.getId();
703 return Arrays.copyOf(filtered, used);
706 // APK expansion files support
708 /** com.android.vending.expansion.zipfile.ZipResourceFile object or null. */
709 private static Object expansionFile;
711 /** com.android.vending.expansion.zipfile.ZipResourceFile's getInputStream() or null. */
712 private static Method expansionFileMethod;
714 /**
715 * This method is called by SDL using JNI.
716 * @return an InputStream on success or null if no expansion file was used.
717 * @throws IOException on errors. Message is set for the SDL error message.
718 */
719 public static InputStream openAPKExpansionInputStream(String fileName) throws IOException {
720 // Get a ZipResourceFile representing a merger of both the main and patch files
721 if (expansionFile == null) {
722 String mainHint = nativeGetHint("SDL_ANDROID_APK_EXPANSION_MAIN_FILE_VERSION");
723 if (mainHint == null) {
724 return null; // no expansion use if no main version was set
726 String patchHint = nativeGetHint("SDL_ANDROID_APK_EXPANSION_PATCH_FILE_VERSION");
727 if (patchHint == null) {
728 return null; // no expansion use if no patch version was set
731 Integer mainVersion;
732 Integer patchVersion;
733 try {
734 mainVersion = Integer.valueOf(mainHint);
735 patchVersion = Integer.valueOf(patchHint);
736 } catch (NumberFormatException ex) {
737 ex.printStackTrace();
738 throw new IOException("No valid file versions set for APK expansion files", ex);
741 try {
742 // To avoid direct dependency on Google APK expansion library that is
743 // not a part of Android SDK we access it using reflection
744 expansionFile = Class.forName("com.android.vending.expansion.zipfile.APKExpansionSupport")
745 .getMethod("getAPKExpansionZipFile", Context.class, int.class, int.class)
746 .invoke(null, SDL.getContext(), mainVersion, patchVersion);
748 expansionFileMethod = expansionFile.getClass()
749 .getMethod("getInputStream", String.class);
750 } catch (Exception ex) {
751 ex.printStackTrace();
752 expansionFile = null;
753 expansionFileMethod = null;
754 throw new IOException("Could not access APK expansion support library", ex);
758 // Get an input stream for a known file inside the expansion file ZIPs
759 InputStream fileStream;
760 try {
761 fileStream = (InputStream)expansionFileMethod.invoke(expansionFile, fileName);
762 } catch (Exception ex) {
763 // calling "getInputStream" failed
764 ex.printStackTrace();
765 throw new IOException("Could not open stream from APK expansion file", ex);
768 if (fileStream == null) {
769 // calling "getInputStream" was successful but null was returned
770 throw new IOException("Could not find path in APK expansion file");
773 return fileStream;
776 // Messagebox
778 /** Result of current messagebox. Also used for blocking the calling thread. */
779 protected final int[] messageboxSelection = new int[1];
781 /** Id of current dialog. */
782 protected int dialogs = 0;
784 /**
785 * This method is called by SDL using JNI.
786 * Shows the messagebox from UI thread and block calling thread.
787 * buttonFlags, buttonIds and buttonTexts must have same length.
788 * @param buttonFlags array containing flags for every button.
789 * @param buttonIds array containing id for every button.
790 * @param buttonTexts array containing text for every button.
791 * @param colors null for default or array of length 5 containing colors.
792 * @return button id or -1.
793 */
794 public int messageboxShowMessageBox(
795 final int flags,
796 final String title,
797 final String message,
798 final int[] buttonFlags,
799 final int[] buttonIds,
800 final String[] buttonTexts,
801 final int[] colors) {
803 messageboxSelection[0] = -1;
805 // sanity checks
807 if ((buttonFlags.length != buttonIds.length) && (buttonIds.length != buttonTexts.length)) {
808 return -1; // implementation broken
811 // collect arguments for Dialog
813 final Bundle args = new Bundle();
814 args.putInt("flags", flags);
815 args.putString("title", title);
816 args.putString("message", message);
817 args.putIntArray("buttonFlags", buttonFlags);
818 args.putIntArray("buttonIds", buttonIds);
819 args.putStringArray("buttonTexts", buttonTexts);
820 args.putIntArray("colors", colors);
822 // trigger Dialog creation on UI thread
824 runOnUiThread(new Runnable() {
825 @Override
826 public void run() {
827 showDialog(dialogs++, args);
829 });
831 // block the calling thread
833 synchronized (messageboxSelection) {
834 try {
835 messageboxSelection.wait();
836 } catch (InterruptedException ex) {
837 ex.printStackTrace();
838 return -1;
842 // return selected value
844 return messageboxSelection[0];
847 @Override
848 protected Dialog onCreateDialog(int ignore, Bundle args) {
850 // TODO set values from "flags" to messagebox dialog
852 // get colors
854 int[] colors = args.getIntArray("colors");
855 int backgroundColor;
856 int textColor;
857 int buttonBorderColor;
858 int buttonBackgroundColor;
859 int buttonSelectedColor;
860 if (colors != null) {
861 int i = -1;
862 backgroundColor = colors[++i];
863 textColor = colors[++i];
864 buttonBorderColor = colors[++i];
865 buttonBackgroundColor = colors[++i];
866 buttonSelectedColor = colors[++i];
867 } else {
868 backgroundColor = Color.TRANSPARENT;
869 textColor = Color.TRANSPARENT;
870 buttonBorderColor = Color.TRANSPARENT;
871 buttonBackgroundColor = Color.TRANSPARENT;
872 buttonSelectedColor = Color.TRANSPARENT;
875 // create dialog with title and a listener to wake up calling thread
877 final Dialog dialog = new Dialog(this);
878 dialog.setTitle(args.getString("title"));
879 dialog.setCancelable(false);
880 dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
881 @Override
882 public void onDismiss(DialogInterface unused) {
883 synchronized (messageboxSelection) {
884 messageboxSelection.notify();
887 });
889 // create text
891 TextView message = new TextView(this);
892 message.setGravity(Gravity.CENTER);
893 message.setText(args.getString("message"));
894 if (textColor != Color.TRANSPARENT) {
895 message.setTextColor(textColor);
898 // create buttons
900 int[] buttonFlags = args.getIntArray("buttonFlags");
901 int[] buttonIds = args.getIntArray("buttonIds");
902 String[] buttonTexts = args.getStringArray("buttonTexts");
904 final SparseArray<Button> mapping = new SparseArray<Button>();
906 LinearLayout buttons = new LinearLayout(this);
907 buttons.setOrientation(LinearLayout.HORIZONTAL);
908 buttons.setGravity(Gravity.CENTER);
909 for (int i = 0; i < buttonTexts.length; ++i) {
910 Button button = new Button(this);
911 final int id = buttonIds[i];
912 button.setOnClickListener(new View.OnClickListener() {
913 @Override
914 public void onClick(View v) {
915 messageboxSelection[0] = id;
916 dialog.dismiss();
918 });
919 if (buttonFlags[i] != 0) {
920 // see SDL_messagebox.h
921 if ((buttonFlags[i] & 0x00000001) != 0) {
922 mapping.put(KeyEvent.KEYCODE_ENTER, button);
924 if ((buttonFlags[i] & 0x00000002) != 0) {
925 mapping.put(KeyEvent.KEYCODE_ESCAPE, button); /* API 11 */
928 button.setText(buttonTexts[i]);
929 if (textColor != Color.TRANSPARENT) {
930 button.setTextColor(textColor);
932 if (buttonBorderColor != Color.TRANSPARENT) {
933 // TODO set color for border of messagebox button
935 if (buttonBackgroundColor != Color.TRANSPARENT) {
936 Drawable drawable = button.getBackground();
937 if (drawable == null) {
938 // setting the color this way removes the style
939 button.setBackgroundColor(buttonBackgroundColor);
940 } else {
941 // setting the color this way keeps the style (gradient, padding, etc.)
942 drawable.setColorFilter(buttonBackgroundColor, PorterDuff.Mode.MULTIPLY);
945 if (buttonSelectedColor != Color.TRANSPARENT) {
946 // TODO set color for selected messagebox button
948 buttons.addView(button);
951 // create content
953 LinearLayout content = new LinearLayout(this);
954 content.setOrientation(LinearLayout.VERTICAL);
955 content.addView(message);
956 content.addView(buttons);
957 if (backgroundColor != Color.TRANSPARENT) {
958 content.setBackgroundColor(backgroundColor);
961 // add content to dialog and return
963 dialog.setContentView(content);
964 dialog.setOnKeyListener(new Dialog.OnKeyListener() {
965 @Override
966 public boolean onKey(DialogInterface d, int keyCode, KeyEvent event) {
967 Button button = mapping.get(keyCode);
968 if (button != null) {
969 if (event.getAction() == KeyEvent.ACTION_UP) {
970 button.performClick();
972 return true; // also for ignored actions
974 return false;
976 });
978 return dialog;
981 /**
982 * This method is called by SDL using JNI.
983 */
984 public static boolean clipboardHasText() {
985 return mClipboardHandler.clipboardHasText();
988 /**
989 * This method is called by SDL using JNI.
990 */
991 public static String clipboardGetText() {
992 return mClipboardHandler.clipboardGetText();
995 /**
996 * This method is called by SDL using JNI.
997 */
998 public static void clipboardSetText(String string) {
999 mClipboardHandler.clipboardSetText(string);
1004 /**
1005 Simple runnable to start the SDL application
1006 */
1007 class SDLMain implements Runnable {
1008 @Override
1009 public void run() {
1010 // Runs SDL_main()
1011 String library = SDLActivity.mSingleton.getMainSharedObject();
1012 String function = SDLActivity.mSingleton.getMainFunction();
1013 String[] arguments = SDLActivity.mSingleton.getArguments();
1015 Log.v("SDL", "Running main function " + function + " from library " + library);
1016 SDLActivity.nativeRunMain(library, function, arguments);
1018 Log.v("SDL", "Finished main function");
1023 /**
1024 SDLSurface. This is what we draw on, so we need to know when it's created
1025 in order to do anything useful.
1027 Because of this, that's where we set up the SDL thread
1028 */
1029 class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
1030 View.OnKeyListener, View.OnTouchListener, SensorEventListener {
1032 // Sensors
1033 protected static SensorManager mSensorManager;
1034 protected static Display mDisplay;
1036 // Keep track of the surface size to normalize touch events
1037 protected static float mWidth, mHeight;
1039 // Startup
1040 public SDLSurface(Context context) {
1041 super(context);
1042 getHolder().addCallback(this);
1044 setFocusable(true);
1045 setFocusableInTouchMode(true);
1046 requestFocus();
1047 setOnKeyListener(this);
1048 setOnTouchListener(this);
1050 mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
1051 mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
1053 if (Build.VERSION.SDK_INT >= 12) {
1054 setOnGenericMotionListener(new SDLGenericMotionListener_API12());
1057 // Some arbitrary defaults to avoid a potential division by zero
1058 mWidth = 1.0f;
1059 mHeight = 1.0f;
1062 public void handlePause() {
1063 enableSensor(Sensor.TYPE_ACCELEROMETER, false);
1066 public void handleResume() {
1067 setFocusable(true);
1068 setFocusableInTouchMode(true);
1069 requestFocus();
1070 setOnKeyListener(this);
1071 setOnTouchListener(this);
1072 enableSensor(Sensor.TYPE_ACCELEROMETER, true);
1075 public Surface getNativeSurface() {
1076 return getHolder().getSurface();
1079 // Called when we have a valid drawing surface
1080 @Override
1081 public void surfaceCreated(SurfaceHolder holder) {
1082 Log.v("SDL", "surfaceCreated()");
1083 holder.setType(SurfaceHolder.SURFACE_TYPE_GPU);
1086 // Called when we lose the surface
1087 @Override
1088 public void surfaceDestroyed(SurfaceHolder holder) {
1089 Log.v("SDL", "surfaceDestroyed()");
1091 // Transition to pause, if needed
1092 SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED;
1093 SDLActivity.handleNativeState();
1095 SDLActivity.mIsSurfaceReady = false;
1096 SDLActivity.onNativeSurfaceDestroyed();
1099 // Called when the surface is resized
1100 @Override
1101 public void surfaceChanged(SurfaceHolder holder,
1102 int format, int width, int height) {
1103 Log.v("SDL", "surfaceChanged()");
1105 int sdlFormat = 0x15151002; // SDL_PIXELFORMAT_RGB565 by default
1106 switch (format) {
1107 case PixelFormat.A_8:
1108 Log.v("SDL", "pixel format A_8");
1109 break;
1110 case PixelFormat.LA_88:
1111 Log.v("SDL", "pixel format LA_88");
1112 break;
1113 case PixelFormat.L_8:
1114 Log.v("SDL", "pixel format L_8");
1115 break;
1116 case PixelFormat.RGBA_4444:
1117 Log.v("SDL", "pixel format RGBA_4444");
1118 sdlFormat = 0x15421002; // SDL_PIXELFORMAT_RGBA4444
1119 break;
1120 case PixelFormat.RGBA_5551:
1121 Log.v("SDL", "pixel format RGBA_5551");
1122 sdlFormat = 0x15441002; // SDL_PIXELFORMAT_RGBA5551
1123 break;
1124 case PixelFormat.RGBA_8888:
1125 Log.v("SDL", "pixel format RGBA_8888");
1126 sdlFormat = 0x16462004; // SDL_PIXELFORMAT_RGBA8888
1127 break;
1128 case PixelFormat.RGBX_8888:
1129 Log.v("SDL", "pixel format RGBX_8888");
1130 sdlFormat = 0x16261804; // SDL_PIXELFORMAT_RGBX8888
1131 break;
1132 case PixelFormat.RGB_332:
1133 Log.v("SDL", "pixel format RGB_332");
1134 sdlFormat = 0x14110801; // SDL_PIXELFORMAT_RGB332
1135 break;
1136 case PixelFormat.RGB_565:
1137 Log.v("SDL", "pixel format RGB_565");
1138 sdlFormat = 0x15151002; // SDL_PIXELFORMAT_RGB565
1139 break;
1140 case PixelFormat.RGB_888:
1141 Log.v("SDL", "pixel format RGB_888");
1142 // Not sure this is right, maybe SDL_PIXELFORMAT_RGB24 instead?
1143 sdlFormat = 0x16161804; // SDL_PIXELFORMAT_RGB888
1144 break;
1145 default:
1146 Log.v("SDL", "pixel format unknown " + format);
1147 break;
1150 mWidth = width;
1151 mHeight = height;
1152 SDLActivity.onNativeResize(width, height, sdlFormat, mDisplay.getRefreshRate());
1153 Log.v("SDL", "Window size: " + width + "x" + height);
1156 boolean skip = false;
1157 int requestedOrientation = SDLActivity.mSingleton.getRequestedOrientation();
1159 if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)
1161 // Accept any
1163 else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT)
1165 if (mWidth > mHeight) {
1166 skip = true;
1168 } else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) {
1169 if (mWidth < mHeight) {
1170 skip = true;
1174 // Special Patch for Square Resolution: Black Berry Passport
1175 if (skip) {
1176 double min = Math.min(mWidth, mHeight);
1177 double max = Math.max(mWidth, mHeight);
1179 if (max / min < 1.20) {
1180 Log.v("SDL", "Don't skip on such aspect-ratio. Could be a square resolution.");
1181 skip = false;
1185 if (skip) {
1186 Log.v("SDL", "Skip .. Surface is not ready.");
1187 SDLActivity.mIsSurfaceReady = false;
1188 return;
1191 /* Surface is ready */
1192 SDLActivity.mIsSurfaceReady = true;
1194 /* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */
1195 SDLActivity.onNativeSurfaceChanged();
1197 SDLActivity.handleNativeState();
1200 // Key events
1201 @Override
1202 public boolean onKey(View v, int keyCode, KeyEvent event) {
1203 // Dispatch the different events depending on where they come from
1204 // Some SOURCE_JOYSTICK, SOURCE_DPAD or SOURCE_GAMEPAD are also SOURCE_KEYBOARD
1205 // So, we try to process them as JOYSTICK/DPAD/GAMEPAD events first, if that fails we try them as KEYBOARD
1206 //
1207 // Furthermore, it's possible a game controller has SOURCE_KEYBOARD and
1208 // SOURCE_JOYSTICK, while its key events arrive from the keyboard source
1209 // So, retrieve the device itself and check all of its sources
1210 if (SDLControllerManager.isDeviceSDLJoystick(event.getDeviceId())) {
1211 // Note that we process events with specific key codes here
1212 if (event.getAction() == KeyEvent.ACTION_DOWN) {
1213 if (SDLControllerManager.onNativePadDown(event.getDeviceId(), keyCode) == 0) {
1214 return true;
1216 } else if (event.getAction() == KeyEvent.ACTION_UP) {
1217 if (SDLControllerManager.onNativePadUp(event.getDeviceId(), keyCode) == 0) {
1218 return true;
1223 if ((event.getSource() & InputDevice.SOURCE_KEYBOARD) != 0) {
1224 if (event.getAction() == KeyEvent.ACTION_DOWN) {
1225 //Log.v("SDL", "key down: " + keyCode);
1226 SDLActivity.onNativeKeyDown(keyCode);
1227 return true;
1229 else if (event.getAction() == KeyEvent.ACTION_UP) {
1230 //Log.v("SDL", "key up: " + keyCode);
1231 SDLActivity.onNativeKeyUp(keyCode);
1232 return true;
1236 if ((event.getSource() & InputDevice.SOURCE_MOUSE) != 0) {
1237 // on some devices key events are sent for mouse BUTTON_BACK/FORWARD presses
1238 // they are ignored here because sending them as mouse input to SDL is messy
1239 if ((keyCode == KeyEvent.KEYCODE_BACK) || (keyCode == KeyEvent.KEYCODE_FORWARD)) {
1240 switch (event.getAction()) {
1241 case KeyEvent.ACTION_DOWN:
1242 case KeyEvent.ACTION_UP:
1243 // mark the event as handled or it will be handled by system
1244 // handling KEYCODE_BACK by system will call onBackPressed()
1245 return true;
1250 return false;
1253 // Touch events
1254 @Override
1255 public boolean onTouch(View v, MotionEvent event) {
1256 /* Ref: http://developer.android.com/training/gestures/multi.html */
1257 final int touchDevId = event.getDeviceId();
1258 final int pointerCount = event.getPointerCount();
1259 int action = event.getActionMasked();
1260 int pointerFingerId;
1261 int mouseButton;
1262 int i = -1;
1263 float x,y,p;
1265 // !!! FIXME: dump this SDK check after 2.0.4 ships and require API14.
1266 if (event.getSource() == InputDevice.SOURCE_MOUSE && SDLActivity.mSeparateMouseAndTouch) {
1267 if (Build.VERSION.SDK_INT < 14) {
1268 mouseButton = 1; // all mouse buttons are the left button
1269 } else {
1270 try {
1271 mouseButton = (Integer) event.getClass().getMethod("getButtonState").invoke(event);
1272 } catch(Exception e) {
1273 mouseButton = 1; // oh well.
1276 SDLActivity.onNativeMouse(mouseButton, action, event.getX(0), event.getY(0));
1277 } else {
1278 switch(action) {
1279 case MotionEvent.ACTION_MOVE:
1280 for (i = 0; i < pointerCount; i++) {
1281 pointerFingerId = event.getPointerId(i);
1282 x = event.getX(i) / mWidth;
1283 y = event.getY(i) / mHeight;
1284 p = event.getPressure(i);
1285 if (p > 1.0f) {
1286 // may be larger than 1.0f on some devices
1287 // see the documentation of getPressure(i)
1288 p = 1.0f;
1290 SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
1292 break;
1294 case MotionEvent.ACTION_UP:
1295 case MotionEvent.ACTION_DOWN:
1296 // Primary pointer up/down, the index is always zero
1297 i = 0;
1298 case MotionEvent.ACTION_POINTER_UP:
1299 case MotionEvent.ACTION_POINTER_DOWN:
1300 // Non primary pointer up/down
1301 if (i == -1) {
1302 i = event.getActionIndex();
1305 pointerFingerId = event.getPointerId(i);
1306 x = event.getX(i) / mWidth;
1307 y = event.getY(i) / mHeight;
1308 p = event.getPressure(i);
1309 if (p > 1.0f) {
1310 // may be larger than 1.0f on some devices
1311 // see the documentation of getPressure(i)
1312 p = 1.0f;
1314 SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
1315 break;
1317 case MotionEvent.ACTION_CANCEL:
1318 for (i = 0; i < pointerCount; i++) {
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, MotionEvent.ACTION_UP, x, y, p);
1330 break;
1332 default:
1333 break;
1337 return true;
1340 // Sensor events
1341 public void enableSensor(int sensortype, boolean enabled) {
1342 // TODO: This uses getDefaultSensor - what if we have >1 accels?
1343 if (enabled) {
1344 mSensorManager.registerListener(this,
1345 mSensorManager.getDefaultSensor(sensortype),
1346 SensorManager.SENSOR_DELAY_GAME, null);
1347 } else {
1348 mSensorManager.unregisterListener(this,
1349 mSensorManager.getDefaultSensor(sensortype));
1353 @Override
1354 public void onAccuracyChanged(Sensor sensor, int accuracy) {
1355 // TODO
1358 @Override
1359 public void onSensorChanged(SensorEvent event) {
1360 if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
1361 float x, y;
1362 switch (mDisplay.getRotation()) {
1363 case Surface.ROTATION_90:
1364 x = -event.values[1];
1365 y = event.values[0];
1366 break;
1367 case Surface.ROTATION_270:
1368 x = event.values[1];
1369 y = -event.values[0];
1370 break;
1371 case Surface.ROTATION_180:
1372 x = -event.values[1];
1373 y = -event.values[0];
1374 break;
1375 default:
1376 x = event.values[0];
1377 y = event.values[1];
1378 break;
1380 SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH,
1381 y / SensorManager.GRAVITY_EARTH,
1382 event.values[2] / SensorManager.GRAVITY_EARTH);
1387 /* This is a fake invisible editor view that receives the input and defines the
1388 * pan&scan region
1389 */
1390 class DummyEdit extends View implements View.OnKeyListener {
1391 InputConnection ic;
1393 public DummyEdit(Context context) {
1394 super(context);
1395 setFocusableInTouchMode(true);
1396 setFocusable(true);
1397 setOnKeyListener(this);
1400 @Override
1401 public boolean onCheckIsTextEditor() {
1402 return true;
1405 @Override
1406 public boolean onKey(View v, int keyCode, KeyEvent event) {
1407 /*
1408 * This handles the hardware keyboard input
1409 */
1410 if (event.getAction() == KeyEvent.ACTION_DOWN) {
1411 if (SDLActivity.isTextInputEvent(event)) {
1412 ic.commitText(String.valueOf((char) event.getUnicodeChar()), 1);
1414 SDLActivity.onNativeKeyDown(keyCode);
1415 return true;
1416 } else if (event.getAction() == KeyEvent.ACTION_UP) {
1417 SDLActivity.onNativeKeyUp(keyCode);
1418 return true;
1420 return false;
1423 //
1424 @Override
1425 public boolean onKeyPreIme (int keyCode, KeyEvent event) {
1426 // As seen on StackOverflow: http://stackoverflow.com/questions/7634346/keyboard-hide-event
1427 // FIXME: Discussion at http://bugzilla.libsdl.org/show_bug.cgi?id=1639
1428 // FIXME: This is not a 100% effective solution to the problem of detecting if the keyboard is showing or not
1429 // FIXME: A more effective solution would be to assume our Layout to be RelativeLayout or LinearLayout
1430 // FIXME: And determine the keyboard presence doing this: http://stackoverflow.com/questions/2150078/how-to-check-visibility-of-software-keyboard-in-android
1431 // FIXME: An even more effective way would be if Android provided this out of the box, but where would the fun be in that :)
1432 if (event.getAction()==KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
1433 if (SDLActivity.mTextEdit != null && SDLActivity.mTextEdit.getVisibility() == View.VISIBLE) {
1434 SDLActivity.onNativeKeyboardFocusLost();
1437 return super.onKeyPreIme(keyCode, event);
1440 @Override
1441 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
1442 ic = new SDLInputConnection(this, true);
1444 outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;
1445 outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI
1446 | EditorInfo.IME_FLAG_NO_FULLSCREEN /* API 11 */;
1448 return ic;
1452 class SDLInputConnection extends BaseInputConnection {
1454 public SDLInputConnection(View targetView, boolean fullEditor) {
1455 super(targetView, fullEditor);
1459 @Override
1460 public boolean sendKeyEvent(KeyEvent event) {
1461 /*
1462 * This handles the keycodes from soft keyboard (and IME-translated input from hardkeyboard)
1463 */
1464 int keyCode = event.getKeyCode();
1465 if (event.getAction() == KeyEvent.ACTION_DOWN) {
1466 if (SDLActivity.isTextInputEvent(event)) {
1467 commitText(String.valueOf((char) event.getUnicodeChar()), 1);
1469 SDLActivity.onNativeKeyDown(keyCode);
1470 return true;
1471 } else if (event.getAction() == KeyEvent.ACTION_UP) {
1472 SDLActivity.onNativeKeyUp(keyCode);
1473 return true;
1475 return super.sendKeyEvent(event);
1478 @Override
1479 public boolean commitText(CharSequence text, int newCursorPosition) {
1481 nativeCommitText(text.toString(), newCursorPosition);
1483 return super.commitText(text, newCursorPosition);
1486 @Override
1487 public boolean setComposingText(CharSequence text, int newCursorPosition) {
1489 nativeSetComposingText(text.toString(), newCursorPosition);
1491 return super.setComposingText(text, newCursorPosition);
1494 public native void nativeCommitText(String text, int newCursorPosition);
1496 public native void nativeSetComposingText(String text, int newCursorPosition);
1498 @Override
1499 public boolean deleteSurroundingText(int beforeLength, int afterLength) {
1500 // Workaround to capture backspace key. Ref: http://stackoverflow.com/questions/14560344/android-backspace-in-webview-baseinputconnection
1501 // and https://bugzilla.libsdl.org/show_bug.cgi?id=2265
1502 if (beforeLength > 0 && afterLength == 0) {
1503 boolean ret = true;
1504 // backspace(s)
1505 while (beforeLength-- > 0) {
1506 boolean ret_key = sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))
1507 && sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));
1508 ret = ret && ret_key;
1510 return ret;
1513 return super.deleteSurroundingText(beforeLength, afterLength);
1517 interface SDLClipboardHandler {
1519 public boolean clipboardHasText();
1520 public String clipboardGetText();
1521 public void clipboardSetText(String string);
1526 class SDLClipboardHandler_API11 implements
1527 SDLClipboardHandler,
1528 android.content.ClipboardManager.OnPrimaryClipChangedListener {
1530 protected android.content.ClipboardManager mClipMgr;
1532 SDLClipboardHandler_API11() {
1533 mClipMgr = (android.content.ClipboardManager) SDL.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
1534 mClipMgr.addPrimaryClipChangedListener(this);
1537 @Override
1538 public boolean clipboardHasText() {
1539 return mClipMgr.hasText();
1542 @Override
1543 public String clipboardGetText() {
1544 CharSequence text;
1545 text = mClipMgr.getText();
1546 if (text != null) {
1547 return text.toString();
1549 return null;
1552 @Override
1553 public void clipboardSetText(String string) {
1554 mClipMgr.removePrimaryClipChangedListener(this);
1555 mClipMgr.setText(string);
1556 mClipMgr.addPrimaryClipChangedListener(this);
1559 @Override
1560 public void onPrimaryClipChanged() {
1561 SDLActivity.onNativeClipboardChanged();
1566 class SDLClipboardHandler_Old implements
1567 SDLClipboardHandler {
1569 protected android.text.ClipboardManager mClipMgrOld;
1571 SDLClipboardHandler_Old() {
1572 mClipMgrOld = (android.text.ClipboardManager) SDL.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
1575 @Override
1576 public boolean clipboardHasText() {
1577 return mClipMgrOld.hasText();
1580 @Override
1581 public String clipboardGetText() {
1582 CharSequence text;
1583 text = mClipMgrOld.getText();
1584 if (text != null) {
1585 return text.toString();
1587 return null;
1590 @Override
1591 public void clipboardSetText(String string) {
1592 mClipMgrOld.setText(string);