comparison android/src/org/libsdl/app/SDLActivity.java @ 1839:78abbabfd58d

Get Android build working again and update for SDL 2.0.7 (last version to support older versions of Android)
author Michael Pavone <pavone@retrodev.com>
date Sun, 14 Apr 2019 23:37:11 -0700
parents 0e5f9d6135be
children 13abdc98379e
comparison
equal deleted inserted replaced
1836:601ef72cc16f 1839:78abbabfd58d
1 package org.libsdl.app; 1 package org.libsdl.app;
2 2
3 import java.util.ArrayList; 3 import java.io.IOException;
4 import java.io.InputStream;
4 import java.util.Arrays; 5 import java.util.Arrays;
5 import java.util.Collections; 6 import java.lang.reflect.Method;
6 import java.util.Comparator; 7 import java.util.Objects;
7 import java.util.List;
8 8
9 import android.app.*; 9 import android.app.*;
10 import android.content.*; 10 import android.content.*;
11 import android.text.InputType;
11 import android.view.*; 12 import android.view.*;
12 import android.view.inputmethod.BaseInputConnection; 13 import android.view.inputmethod.BaseInputConnection;
13 import android.view.inputmethod.EditorInfo; 14 import android.view.inputmethod.EditorInfo;
14 import android.view.inputmethod.InputConnection; 15 import android.view.inputmethod.InputConnection;
15 import android.view.inputmethod.InputMethodManager; 16 import android.view.inputmethod.InputMethodManager;
16 import android.widget.AbsoluteLayout; 17 import android.widget.RelativeLayout;
18 import android.widget.Button;
19 import android.widget.LinearLayout;
20 import android.widget.TextView;
17 import android.os.*; 21 import android.os.*;
18 import android.util.Log; 22 import android.util.Log;
23 import android.util.SparseArray;
19 import android.graphics.*; 24 import android.graphics.*;
20 import android.media.*; 25 import android.graphics.drawable.Drawable;
21 import android.hardware.*; 26 import android.hardware.*;
22 27 import android.content.pm.ActivityInfo;
23 28
24 /** 29 /**
25 SDL Activity 30 SDL Activity
26 */ 31 */
27 public class SDLActivity extends Activity { 32 public class SDLActivity extends Activity {
28 private static final String TAG = "SDL"; 33 private static final String TAG = "SDL";
29 34
30 // Keep track of the paused state 35 public static boolean mIsResumedCalled, mIsSurfaceReady, mHasFocus;
31 public static boolean mIsPaused, mIsSurfaceReady, mHasFocus; 36
37 // Handle the state of the native layer
38 public enum NativeState {
39 INIT, RESUMED, PAUSED
40 }
41
42 public static NativeState mNextNativeState;
43 public static NativeState mCurrentNativeState;
44
32 public static boolean mExitCalledFromJava; 45 public static boolean mExitCalledFromJava;
46
47 /** If shared libraries (e.g. SDL or the native application) could not be loaded. */
48 public static boolean mBrokenLibraries;
49
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;
33 53
34 // Main components 54 // Main components
35 protected static SDLActivity mSingleton; 55 protected static SDLActivity mSingleton;
36 protected static SDLSurface mSurface; 56 protected static SDLSurface mSurface;
37 protected static View mTextEdit; 57 protected static View mTextEdit;
58 protected static boolean mScreenKeyboardShown;
38 protected static ViewGroup mLayout; 59 protected static ViewGroup mLayout;
39 protected static SDLJoystickHandler mJoystickHandler; 60 protected static SDLClipboardHandler mClipboardHandler;
61
40 62
41 // This is what SDL runs in. It invokes SDL_main(), eventually 63 // This is what SDL runs in. It invokes SDL_main(), eventually
42 protected static Thread mSDLThread; 64 protected static Thread mSDLThread;
43 65
44 // Audio 66 /**
45 protected static AudioTrack mAudioTrack; 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 }
80
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 }
88
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 };
106 }
46 107
47 // Load the .so 108 // Load the .so
48 static { 109 public void loadLibraries() {
49 System.loadLibrary("SDL2"); 110 for (String lib : getLibraries()) {
50 //System.loadLibrary("SDL2_image"); 111 System.loadLibrary(lib);
51 //System.loadLibrary("SDL2_mixer"); 112 }
52 //System.loadLibrary("SDL2_net"); 113 }
53 //System.loadLibrary("SDL2_ttf"); 114
54 System.loadLibrary("main"); 115 /**
55 } 116 * This method is called by SDL before starting the native application thread.
56 117 * It can be overridden to provide the arguments after the application name.
57 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];
123 }
124
58 public static void initialize() { 125 public static void initialize() {
59 // The static nature of the singleton and Android quirkyness force us to initialize everything here 126 // The static nature of the singleton and Android quirkyness force us to initialize everything here
60 // Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values 127 // Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values
61 mSingleton = null; 128 mSingleton = null;
62 mSurface = null; 129 mSurface = null;
63 mTextEdit = null; 130 mTextEdit = null;
64 mLayout = null; 131 mLayout = null;
65 mJoystickHandler = null; 132 mClipboardHandler = null;
66 mSDLThread = null; 133 mSDLThread = null;
67 mAudioTrack = null;
68 mExitCalledFromJava = false; 134 mExitCalledFromJava = false;
69 mIsPaused = false; 135 mBrokenLibraries = false;
136 mIsResumedCalled = false;
70 mIsSurfaceReady = false; 137 mIsSurfaceReady = false;
71 mHasFocus = true; 138 mHasFocus = true;
139 mNextNativeState = NativeState.INIT;
140 mCurrentNativeState = NativeState.INIT;
72 } 141 }
73 142
74 // Setup 143 // Setup
75 @Override 144 @Override
76 protected void onCreate(Bundle savedInstanceState) { 145 protected void onCreate(Bundle savedInstanceState) {
77 Log.v("SDL", "onCreate():" + mSingleton); 146 Log.v(TAG, "Device: " + android.os.Build.DEVICE);
147 Log.v(TAG, "Model: " + android.os.Build.MODEL);
148 Log.v(TAG, "onCreate()");
78 super.onCreate(savedInstanceState); 149 super.onCreate(savedInstanceState);
79 150
80 SDLActivity.initialize(); 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();
163 }
164
165 if (mBrokenLibraries)
166 {
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();
179 }
180 });
181 dlgAlert.setCancelable(false);
182 dlgAlert.create().show();
183
184 return;
185 }
186
187 // Set up JNI
188 SDL.setupJNI();
189
190 // Initialize state
191 SDL.initialize();
192
81 // So we can call stuff from static callbacks 193 // So we can call stuff from static callbacks
82 mSingleton = this; 194 mSingleton = this;
195 SDL.setContext(this);
196
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();
202 }
83 203
84 // Set up the surface 204 // Set up the surface
85 mSurface = new SDLSurface(getApplication()); 205 mSurface = new SDLSurface(getApplication());
206
207 mLayout = new RelativeLayout(this);
208 mLayout.addView(mSurface);
209
210 setContentView(mLayout);
86 211
87 if(Build.VERSION.SDK_INT >= 12) { 212 // Get filename from "Open with" of another application
88 mJoystickHandler = new SDLJoystickHandler_API12(); 213 Intent intent = getIntent();
89 } 214 if (intent != null && intent.getData() != null) {
90 else { 215 String filename = intent.getData().getPath();
91 mJoystickHandler = new SDLJoystickHandler(); 216 if (filename != null) {
92 } 217 Log.v(TAG, "Got filename: " + filename);
93 218 SDLActivity.onNativeDropFile(filename);
94 mLayout = new AbsoluteLayout(this); 219 }
95 mLayout.addView(mSurface); 220 }
96
97 setContentView(mLayout);
98 } 221 }
99 222
100 // Events 223 // Events
101 @Override 224 @Override
102 protected void onPause() { 225 protected void onPause() {
103 Log.v("SDL", "onPause()"); 226 Log.v(TAG, "onPause()");
104 super.onPause(); 227 super.onPause();
105 SDLActivity.handlePause(); 228 mNextNativeState = NativeState.PAUSED;
229 mIsResumedCalled = false;
230
231 if (SDLActivity.mBrokenLibraries) {
232 return;
233 }
234
235 SDLActivity.handleNativeState();
106 } 236 }
107 237
108 @Override 238 @Override
109 protected void onResume() { 239 protected void onResume() {
110 Log.v("SDL", "onResume()"); 240 Log.v(TAG, "onResume()");
111 super.onResume(); 241 super.onResume();
112 SDLActivity.handleResume(); 242 mNextNativeState = NativeState.RESUMED;
243 mIsResumedCalled = true;
244
245 if (SDLActivity.mBrokenLibraries) {
246 return;
247 }
248
249 SDLActivity.handleNativeState();
113 } 250 }
114 251
115 252
116 @Override 253 @Override
117 public void onWindowFocusChanged(boolean hasFocus) { 254 public void onWindowFocusChanged(boolean hasFocus) {
118 super.onWindowFocusChanged(hasFocus); 255 super.onWindowFocusChanged(hasFocus);
119 Log.v("SDL", "onWindowFocusChanged(): " + hasFocus); 256 Log.v(TAG, "onWindowFocusChanged(): " + hasFocus);
257
258 if (SDLActivity.mBrokenLibraries) {
259 return;
260 }
120 261
121 SDLActivity.mHasFocus = hasFocus; 262 SDLActivity.mHasFocus = hasFocus;
122 if (hasFocus) { 263 if (hasFocus) {
123 SDLActivity.handleResume(); 264 mNextNativeState = NativeState.RESUMED;
124 } 265 } else {
266 mNextNativeState = NativeState.PAUSED;
267 }
268
269 SDLActivity.handleNativeState();
125 } 270 }
126 271
127 @Override 272 @Override
128 public void onLowMemory() { 273 public void onLowMemory() {
129 Log.v("SDL", "onLowMemory()"); 274 Log.v(TAG, "onLowMemory()");
130 super.onLowMemory(); 275 super.onLowMemory();
276
277 if (SDLActivity.mBrokenLibraries) {
278 return;
279 }
280
131 SDLActivity.nativeLowMemory(); 281 SDLActivity.nativeLowMemory();
132 } 282 }
133 283
134 @Override 284 @Override
135 protected void onDestroy() { 285 protected void onDestroy() {
136 Log.v("SDL", "onDestroy()"); 286 Log.v(TAG, "onDestroy()");
287
288 if (SDLActivity.mBrokenLibraries) {
289 super.onDestroy();
290 // Reset everything in case the user re opens the app
291 SDLActivity.initialize();
292 return;
293 }
294
295 mNextNativeState = NativeState.PAUSED;
296 SDLActivity.handleNativeState();
297
137 // Send a quit message to the application 298 // Send a quit message to the application
138 SDLActivity.mExitCalledFromJava = true; 299 SDLActivity.mExitCalledFromJava = true;
139 SDLActivity.nativeQuit(); 300 SDLActivity.nativeQuit();
140 301
141 // Now wait for the SDL thread to quit 302 // Now wait for the SDL thread to quit
142 if (SDLActivity.mSDLThread != null) { 303 if (SDLActivity.mSDLThread != null) {
143 try { 304 try {
144 SDLActivity.mSDLThread.join(); 305 SDLActivity.mSDLThread.join();
145 } catch(Exception e) { 306 } catch(Exception e) {
146 Log.v("SDL", "Problem stopping thread: " + e); 307 Log.v(TAG, "Problem stopping thread: " + e);
147 } 308 }
148 SDLActivity.mSDLThread = null; 309 SDLActivity.mSDLThread = null;
149 310
150 //Log.v("SDL", "Finished waiting for SDL thread"); 311 //Log.v(TAG, "Finished waiting for SDL thread");
151 } 312 }
152 313
153 super.onDestroy(); 314 super.onDestroy();
315
154 // Reset everything in case the user re opens the app 316 // Reset everything in case the user re opens the app
155 SDLActivity.initialize(); 317 SDLActivity.initialize();
156 } 318 }
157 319
158 @Override 320 @Override
159 public boolean dispatchKeyEvent(KeyEvent event) { 321 public boolean dispatchKeyEvent(KeyEvent event) {
322
323 if (SDLActivity.mBrokenLibraries) {
324 return false;
325 }
326
160 int keyCode = event.getKeyCode(); 327 int keyCode = event.getKeyCode();
161 // Ignore certain special keys so they're handled by Android 328 // Ignore certain special keys so they're handled by Android
162 if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || 329 if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN ||
163 keyCode == KeyEvent.KEYCODE_VOLUME_UP || 330 keyCode == KeyEvent.KEYCODE_VOLUME_UP ||
164 keyCode == KeyEvent.KEYCODE_CAMERA || 331 keyCode == KeyEvent.KEYCODE_CAMERA ||
165 keyCode == 168 || /* API 11: KeyEvent.KEYCODE_ZOOM_IN */ 332 keyCode == KeyEvent.KEYCODE_ZOOM_IN || /* API 11 */
166 keyCode == 169 /* API 11: KeyEvent.KEYCODE_ZOOM_OUT */ 333 keyCode == KeyEvent.KEYCODE_ZOOM_OUT /* API 11 */
167 ) { 334 ) {
168 return false; 335 return false;
169 } 336 }
170 return super.dispatchKeyEvent(event); 337 return super.dispatchKeyEvent(event);
171 } 338 }
172 339
173 /** Called by onPause or surfaceDestroyed. Even if surfaceDestroyed 340 /* Transition to next state */
174 * is the first to be called, mIsSurfaceReady should still be set 341 public static void handleNativeState() {
175 * to 'true' during the call to onPause (in a usual scenario). 342
176 */ 343 if (mNextNativeState == mCurrentNativeState) {
177 public static void handlePause() { 344 // Already in same state, discard.
178 if (!SDLActivity.mIsPaused && SDLActivity.mIsSurfaceReady) { 345 return;
179 SDLActivity.mIsPaused = true; 346 }
180 SDLActivity.nativePause(); 347
181 mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, false); 348 // Try a transition to init state
182 } 349 if (mNextNativeState == NativeState.INIT) {
183 } 350
184 351 mCurrentNativeState = mNextNativeState;
185 /** Called by onResume or surfaceCreated. An actual resume should be done only when the surface is ready. 352 return;
186 * Note: Some Android variants may send multiple surfaceChanged events, so we don't need to resume 353 }
187 * every time we get one of those events, only if it comes after surfaceDestroyed 354
188 */ 355 // Try a transition to paused state
189 public static void handleResume() { 356 if (mNextNativeState == NativeState.PAUSED) {
190 if (SDLActivity.mIsPaused && SDLActivity.mIsSurfaceReady && SDLActivity.mHasFocus) { 357 nativePause();
191 SDLActivity.mIsPaused = false; 358 mSurface.handlePause();
192 SDLActivity.nativeResume(); 359 mCurrentNativeState = mNextNativeState;
193 mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, true); 360 return;
194 } 361 }
195 } 362
196 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?
370
371 final Thread sdlThread = new Thread(new SDLMain(), "SDLThread");
372 mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, true);
373 sdlThread.start();
374
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();
387 }
388 }
389 }
390 }, "SDLThreadListener");
391
392 mSDLThread.start();
393 }
394
395 nativeResume();
396 mSurface.handleResume();
397 mCurrentNativeState = mNextNativeState;
398 }
399 }
400 }
401
197 /* The native thread has finished */ 402 /* The native thread has finished */
198 public static void handleNativeExit() { 403 public static void handleNativeExit() {
199 SDLActivity.mSDLThread = null; 404 SDLActivity.mSDLThread = null;
200 mSingleton.finish(); 405 mSingleton.finish();
201 } 406 }
203 408
204 // Messages from the SDLMain thread 409 // Messages from the SDLMain thread
205 static final int COMMAND_CHANGE_TITLE = 1; 410 static final int COMMAND_CHANGE_TITLE = 1;
206 static final int COMMAND_UNUSED = 2; 411 static final int COMMAND_UNUSED = 2;
207 static final int COMMAND_TEXTEDIT_HIDE = 3; 412 static final int COMMAND_TEXTEDIT_HIDE = 3;
413 static final int COMMAND_SET_KEEP_SCREEN_ON = 5;
208 414
209 protected static final int COMMAND_USER = 0x8000; 415 protected static final int COMMAND_USER = 0x8000;
210 416
211 /** 417 /**
212 * This method is called by SDL if SDL did not handle a message itself. 418 * This method is called by SDL if SDL did not handle a message itself.
226 * static to prevent implicit references to enclosing object. 432 * static to prevent implicit references to enclosing object.
227 */ 433 */
228 protected static class SDLCommandHandler extends Handler { 434 protected static class SDLCommandHandler extends Handler {
229 @Override 435 @Override
230 public void handleMessage(Message msg) { 436 public void handleMessage(Message msg) {
231 Context context = getContext(); 437 Context context = SDL.getContext();
232 if (context == null) { 438 if (context == null) {
233 Log.e(TAG, "error handling message, getContext() returned null"); 439 Log.e(TAG, "error handling message, getContext() returned null");
234 return; 440 return;
235 } 441 }
236 switch (msg.arg1) { 442 switch (msg.arg1) {
241 Log.e(TAG, "error handling message, getContext() returned no Activity"); 447 Log.e(TAG, "error handling message, getContext() returned no Activity");
242 } 448 }
243 break; 449 break;
244 case COMMAND_TEXTEDIT_HIDE: 450 case COMMAND_TEXTEDIT_HIDE:
245 if (mTextEdit != null) { 451 if (mTextEdit != null) {
246 mTextEdit.setVisibility(View.GONE); 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));
247 456
248 InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); 457 InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
249 imm.hideSoftInputFromWindow(mTextEdit.getWindowToken(), 0); 458 imm.hideSoftInputFromWindow(mTextEdit.getWindowToken(), 0);
459
460 mScreenKeyboardShown = false;
250 } 461 }
251 break; 462 break;
252 463 case COMMAND_SET_KEEP_SCREEN_ON:
464 {
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);
472 }
473 }
474 }
475 break;
476 }
253 default: 477 default:
254 if ((context instanceof SDLActivity) && !((SDLActivity) context).onUnhandledMessage(msg.arg1, msg.obj)) { 478 if ((context instanceof SDLActivity) && !((SDLActivity) context).onUnhandledMessage(msg.arg1, msg.obj)) {
255 Log.e(TAG, "error handling message, command is " + msg.arg1); 479 Log.e(TAG, "error handling message, command is " + msg.arg1);
256 } 480 }
257 } 481 }
268 msg.obj = data; 492 msg.obj = data;
269 return commandHandler.sendMessage(msg); 493 return commandHandler.sendMessage(msg);
270 } 494 }
271 495
272 // C functions we call 496 // C functions we call
273 public static native void nativeInit(); 497 public static native int nativeSetupJNI();
498 public static native int nativeRunMain(String library, String function, Object arguments);
274 public static native void nativeLowMemory(); 499 public static native void nativeLowMemory();
275 public static native void nativeQuit(); 500 public static native void nativeQuit();
276 public static native void nativePause(); 501 public static native void nativePause();
277 public static native void nativeResume(); 502 public static native void nativeResume();
278 public static native void onNativeResize(int x, int y, int format); 503 public static native void onNativeDropFile(String filename);
279 public static native int onNativePadDown(int device_id, int keycode); 504 public static native void onNativeResize(int x, int y, int format, float rate);
280 public static native int onNativePadUp(int device_id, int keycode);
281 public static native void onNativeJoy(int device_id, int axis,
282 float value);
283 public static native void onNativeHat(int device_id, int hat_id,
284 int x, int y);
285 public static native void onNativeKeyDown(int keycode); 505 public static native void onNativeKeyDown(int keycode);
286 public static native void onNativeKeyUp(int keycode); 506 public static native void onNativeKeyUp(int keycode);
287 public static native void onNativeKeyboardFocusLost(); 507 public static native void onNativeKeyboardFocusLost();
508 public static native void onNativeMouse(int button, int action, float x, float y);
288 public static native void onNativeTouch(int touchDevId, int pointerFingerId, 509 public static native void onNativeTouch(int touchDevId, int pointerFingerId,
289 int action, float x, 510 int action, float x,
290 float y, float p); 511 float y, float p);
291 public static native void onNativeAccel(float x, float y, float z); 512 public static native void onNativeAccel(float x, float y, float z);
513 public static native void onNativeClipboardChanged();
292 public static native void onNativeSurfaceChanged(); 514 public static native void onNativeSurfaceChanged();
293 public static native void onNativeSurfaceDestroyed(); 515 public static native void onNativeSurfaceDestroyed();
294 public static native void nativeFlipBuffers(); 516 public static native String nativeGetHint(String name);
295 public static native int nativeAddJoystick(int device_id, String name, 517
296 int is_accelerometer, int nbuttons, 518 /**
297 int naxes, int nhats, int nballs); 519 * This method is called by SDL using JNI.
298 public static native int nativeRemoveJoystick(int device_id); 520 */
299
300 public static void flipBuffers() {
301 SDLActivity.nativeFlipBuffers();
302 }
303
304 public static boolean setActivityTitle(String title) { 521 public static boolean setActivityTitle(String title) {
305 // Called from SDLMain() thread and can't directly affect the view 522 // Called from SDLMain() thread and can't directly affect the view
306 return mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title); 523 return mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title);
307 } 524 }
308 525
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)
532 {
533 if (mSingleton != null) {
534 mSingleton.setOrientationBis(w, h, resizable, hint);
535 }
536 }
537
538 /**
539 * This can be overridden
540 */
541 public void setOrientationBis(int w, int h, boolean resizable, String hint)
542 {
543 int orientation = -1;
544
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;
558 }
559 }
560
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;
570 }
571 }
572 }
573
574 Log.v("SDL", "setOrientation() orientation=" + orientation + " width=" + w +" height="+ h +" resizable=" + resizable + " hint=" + hint);
575 if (orientation != -1) {
576 mSingleton.setRequestedOrientation(orientation);
577 }
578 }
579
580
581 /**
582 * This method is called by SDL using JNI.
583 */
584 public static boolean isScreenKeyboardShown()
585 {
586 if (mTextEdit == null) {
587 return false;
588 }
589
590 if (!mScreenKeyboardShown) {
591 return false;
592 }
593
594 InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
595 return imm.isAcceptingText();
596
597 }
598
599 /**
600 * This method is called by SDL using JNI.
601 */
309 public static boolean sendMessage(int command, int param) { 602 public static boolean sendMessage(int command, int param) {
603 if (mSingleton == null) {
604 return false;
605 }
310 return mSingleton.sendCommand(command, Integer.valueOf(param)); 606 return mSingleton.sendCommand(command, Integer.valueOf(param));
311 } 607 }
312 608
609 /**
610 * This method is called by SDL using JNI.
611 */
313 public static Context getContext() { 612 public static Context getContext() {
314 return mSingleton; 613 return SDL.getContext();
315 }
316
317 /**
318 * @return result of getSystemService(name) but executed on UI thread.
319 */
320 public Object getSystemServiceFromUiThread(final String name) {
321 final Object lock = new Object();
322 final Object[] results = new Object[2]; // array for writable variables
323 synchronized (lock) {
324 runOnUiThread(new Runnable() {
325 @Override
326 public void run() {
327 synchronized (lock) {
328 results[0] = getSystemService(name);
329 results[1] = Boolean.TRUE;
330 lock.notify();
331 }
332 }
333 });
334 if (results[1] == null) {
335 try {
336 lock.wait();
337 } catch (InterruptedException ex) {
338 ex.printStackTrace();
339 }
340 }
341 }
342 return results[0];
343 } 614 }
344 615
345 static class ShowTextInputTask implements Runnable { 616 static class ShowTextInputTask implements Runnable {
346 /* 617 /*
347 * This is used to regulate the pan&scan method to have some offset from 618 * This is used to regulate the pan&scan method to have some offset from
359 this.h = h; 630 this.h = h;
360 } 631 }
361 632
362 @Override 633 @Override
363 public void run() { 634 public void run() {
364 AbsoluteLayout.LayoutParams params = new AbsoluteLayout.LayoutParams( 635 RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(w, h + HEIGHT_PADDING);
365 w, h + HEIGHT_PADDING, x, y); 636 params.leftMargin = x;
637 params.topMargin = y;
366 638
367 if (mTextEdit == null) { 639 if (mTextEdit == null) {
368 mTextEdit = new DummyEdit(getContext()); 640 mTextEdit = new DummyEdit(SDL.getContext());
369 641
370 mLayout.addView(mTextEdit, params); 642 mLayout.addView(mTextEdit, params);
371 } else { 643 } else {
372 mTextEdit.setLayoutParams(params); 644 mTextEdit.setLayoutParams(params);
373 } 645 }
374 646
375 mTextEdit.setVisibility(View.VISIBLE); 647 mTextEdit.setVisibility(View.VISIBLE);
376 mTextEdit.requestFocus(); 648 mTextEdit.requestFocus();
377 649
378 InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 650 InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
379 imm.showSoftInput(mTextEdit, 0); 651 imm.showSoftInput(mTextEdit, 0);
380 } 652
381 } 653 mScreenKeyboardShown = true;
382 654 }
655 }
656
657 /**
658 * This method is called by SDL using JNI.
659 */
383 public static boolean showTextInput(int x, int y, int w, int h) { 660 public static boolean showTextInput(int x, int y, int w, int h) {
384 // Transfer the task to the main thread as a Runnable 661 // Transfer the task to the main thread as a Runnable
385 return mSingleton.commandHandler.post(new ShowTextInputTask(x, y, w, h)); 662 return mSingleton.commandHandler.post(new ShowTextInputTask(x, y, w, h));
386 } 663 }
387 664
665 public static boolean isTextInputEvent(KeyEvent event) {
666
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 }
672 }
673
674 return event.isPrintingKey() || event.getKeyCode() == KeyEvent.KEYCODE_SPACE;
675 }
676
677 /**
678 * This method is called by SDL using JNI.
679 */
388 public static Surface getNativeSurface() { 680 public static Surface getNativeSurface() {
681 if (SDLActivity.mSurface == null) {
682 return null;
683 }
389 return SDLActivity.mSurface.getNativeSurface(); 684 return SDLActivity.mSurface.getNativeSurface();
390 } 685 }
391 686
392 // Audio
393 public static int audioInit(int sampleRate, boolean is16Bit, boolean isStereo, int desiredFrames) {
394 int channelConfig = isStereo ? AudioFormat.CHANNEL_CONFIGURATION_STEREO : AudioFormat.CHANNEL_CONFIGURATION_MONO;
395 int audioFormat = is16Bit ? AudioFormat.ENCODING_PCM_16BIT : AudioFormat.ENCODING_PCM_8BIT;
396 int frameSize = (isStereo ? 2 : 1) * (is16Bit ? 2 : 1);
397
398 Log.v("SDL", "SDL audio: wanted " + (isStereo ? "stereo" : "mono") + " " + (is16Bit ? "16-bit" : "8-bit") + " " + (sampleRate / 1000f) + "kHz, " + desiredFrames + " frames buffer");
399
400 // Let the user pick a larger buffer if they really want -- but ye
401 // gods they probably shouldn't, the minimums are horrifyingly high
402 // latency already
403 desiredFrames = Math.max(desiredFrames, (AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat) + frameSize - 1) / frameSize);
404
405 if (mAudioTrack == null) {
406 mAudioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, sampleRate,
407 channelConfig, audioFormat, desiredFrames * frameSize, AudioTrack.MODE_STREAM);
408
409 // Instantiating AudioTrack can "succeed" without an exception and the track may still be invalid
410 // Ref: https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/media/java/android/media/AudioTrack.java
411 // Ref: http://developer.android.com/reference/android/media/AudioTrack.html#getState()
412
413 if (mAudioTrack.getState() != AudioTrack.STATE_INITIALIZED) {
414 Log.e("SDL", "Failed during initialization of Audio Track");
415 mAudioTrack = null;
416 return -1;
417 }
418
419 mAudioTrack.play();
420 }
421
422 Log.v("SDL", "SDL audio: got " + ((mAudioTrack.getChannelCount() >= 2) ? "stereo" : "mono") + " " + ((mAudioTrack.getAudioFormat() == AudioFormat.ENCODING_PCM_16BIT) ? "16-bit" : "8-bit") + " " + (mAudioTrack.getSampleRate() / 1000f) + "kHz, " + desiredFrames + " frames buffer");
423
424 return 0;
425 }
426
427 public static void audioWriteShortBuffer(short[] buffer) {
428 for (int i = 0; i < buffer.length; ) {
429 int result = mAudioTrack.write(buffer, i, buffer.length - i);
430 if (result > 0) {
431 i += result;
432 } else if (result == 0) {
433 try {
434 Thread.sleep(1);
435 } catch(InterruptedException e) {
436 // Nom nom
437 }
438 } else {
439 Log.w("SDL", "SDL audio: error return from write(short)");
440 return;
441 }
442 }
443 }
444
445 public static void audioWriteByteBuffer(byte[] buffer) {
446 for (int i = 0; i < buffer.length; ) {
447 int result = mAudioTrack.write(buffer, i, buffer.length - i);
448 if (result > 0) {
449 i += result;
450 } else if (result == 0) {
451 try {
452 Thread.sleep(1);
453 } catch(InterruptedException e) {
454 // Nom nom
455 }
456 } else {
457 Log.w("SDL", "SDL audio: error return from write(byte)");
458 return;
459 }
460 }
461 }
462
463 public static void audioQuit() {
464 if (mAudioTrack != null) {
465 mAudioTrack.stop();
466 mAudioTrack = null;
467 }
468 }
469
470 // Input 687 // Input
471 688
472 /** 689 /**
690 * This method is called by SDL using JNI.
473 * @return an array which may be empty but is never null. 691 * @return an array which may be empty but is never null.
474 */ 692 */
475 public static int[] inputGetInputDeviceIds(int sources) { 693 public static int[] inputGetInputDeviceIds(int sources) {
476 int[] ids = InputDevice.getDeviceIds(); 694 int[] ids = InputDevice.getDeviceIds();
477 int[] filtered = new int[ids.length]; 695 int[] filtered = new int[ids.length];
482 filtered[used++] = device.getId(); 700 filtered[used++] = device.getId();
483 } 701 }
484 } 702 }
485 return Arrays.copyOf(filtered, used); 703 return Arrays.copyOf(filtered, used);
486 } 704 }
487 705
488 // Joystick glue code, just a series of stubs that redirect to the SDLJoystickHandler instance 706 // APK expansion files support
489 public static boolean handleJoystickMotionEvent(MotionEvent event) { 707
490 return mJoystickHandler.handleMotionEvent(event); 708 /** com.android.vending.expansion.zipfile.ZipResourceFile object or null. */
709 private static Object expansionFile;
710
711 /** com.android.vending.expansion.zipfile.ZipResourceFile's getInputStream() or null. */
712 private static Method expansionFileMethod;
713
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
725 }
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
729 }
730
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);
739 }
740
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);
747
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);
755 }
756 }
757
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);
766 }
767
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");
771 }
772
773 return fileStream;
774 }
775
776 // Messagebox
777
778 /** Result of current messagebox. Also used for blocking the calling thread. */
779 protected final int[] messageboxSelection = new int[1];
780
781 /** Id of current dialog. */
782 protected int dialogs = 0;
783
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) {
802
803 messageboxSelection[0] = -1;
804
805 // sanity checks
806
807 if ((buttonFlags.length != buttonIds.length) && (buttonIds.length != buttonTexts.length)) {
808 return -1; // implementation broken
809 }
810
811 // collect arguments for Dialog
812
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);
821
822 // trigger Dialog creation on UI thread
823
824 runOnUiThread(new Runnable() {
825 @Override
826 public void run() {
827 showDialog(dialogs++, args);
828 }
829 });
830
831 // block the calling thread
832
833 synchronized (messageboxSelection) {
834 try {
835 messageboxSelection.wait();
836 } catch (InterruptedException ex) {
837 ex.printStackTrace();
838 return -1;
839 }
840 }
841
842 // return selected value
843
844 return messageboxSelection[0];
845 }
846
847 @Override
848 protected Dialog onCreateDialog(int ignore, Bundle args) {
849
850 // TODO set values from "flags" to messagebox dialog
851
852 // get colors
853
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;
873 }
874
875 // create dialog with title and a listener to wake up calling thread
876
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();
885 }
886 }
887 });
888
889 // create text
890
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);
896 }
897
898 // create buttons
899
900 int[] buttonFlags = args.getIntArray("buttonFlags");
901 int[] buttonIds = args.getIntArray("buttonIds");
902 String[] buttonTexts = args.getStringArray("buttonTexts");
903
904 final SparseArray<Button> mapping = new SparseArray<Button>();
905
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();
917 }
918 });
919 if (buttonFlags[i] != 0) {
920 // see SDL_messagebox.h
921 if ((buttonFlags[i] & 0x00000001) != 0) {
922 mapping.put(KeyEvent.KEYCODE_ENTER, button);
923 }
924 if ((buttonFlags[i] & 0x00000002) != 0) {
925 mapping.put(KeyEvent.KEYCODE_ESCAPE, button); /* API 11 */
926 }
927 }
928 button.setText(buttonTexts[i]);
929 if (textColor != Color.TRANSPARENT) {
930 button.setTextColor(textColor);
931 }
932 if (buttonBorderColor != Color.TRANSPARENT) {
933 // TODO set color for border of messagebox button
934 }
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);
943 }
944 }
945 if (buttonSelectedColor != Color.TRANSPARENT) {
946 // TODO set color for selected messagebox button
947 }
948 buttons.addView(button);
949 }
950
951 // create content
952
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);
959 }
960
961 // add content to dialog and return
962
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();
971 }
972 return true; // also for ignored actions
973 }
974 return false;
975 }
976 });
977
978 return dialog;
979 }
980
981 /**
982 * This method is called by SDL using JNI.
983 */
984 public static boolean clipboardHasText() {
985 return mClipboardHandler.clipboardHasText();
491 } 986 }
492 987
493 public static void pollInputDevices() { 988 /**
494 if (SDLActivity.mSDLThread != null) { 989 * This method is called by SDL using JNI.
495 mJoystickHandler.pollInputDevices(); 990 */
496 } 991 public static String clipboardGetText() {
497 } 992 return mClipboardHandler.clipboardGetText();
498 993 }
994
995 /**
996 * This method is called by SDL using JNI.
997 */
998 public static void clipboardSetText(String string) {
999 mClipboardHandler.clipboardSetText(string);
1000 }
1001
499 } 1002 }
500 1003
501 /** 1004 /**
502 Simple nativeInit() runnable 1005 Simple runnable to start the SDL application
503 */ 1006 */
504 class SDLMain implements Runnable { 1007 class SDLMain implements Runnable {
505 @Override 1008 @Override
506 public void run() { 1009 public void run() {
507 // Runs SDL_main() 1010 // Runs SDL_main()
508 SDLActivity.nativeInit(); 1011 String library = SDLActivity.mSingleton.getMainSharedObject();
509 1012 String function = SDLActivity.mSingleton.getMainFunction();
510 //Log.v("SDL", "SDL thread terminated"); 1013 String[] arguments = SDLActivity.mSingleton.getArguments();
1014
1015 Log.v("SDL", "Running main function " + function + " from library " + library);
1016 SDLActivity.nativeRunMain(library, function, arguments);
1017
1018 Log.v("SDL", "Finished main function");
511 } 1019 }
512 } 1020 }
513 1021
514 1022
515 /** 1023 /**
516 SDLSurface. This is what we draw on, so we need to know when it's created 1024 SDLSurface. This is what we draw on, so we need to know when it's created
517 in order to do anything useful. 1025 in order to do anything useful.
518 1026
519 Because of this, that's where we set up the SDL thread 1027 Because of this, that's where we set up the SDL thread
520 */ 1028 */
521 class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, 1029 class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
522 View.OnKeyListener, View.OnTouchListener, SensorEventListener { 1030 View.OnKeyListener, View.OnTouchListener, SensorEventListener {
523 1031
524 // Sensors 1032 // Sensors
525 protected static SensorManager mSensorManager; 1033 protected static SensorManager mSensorManager;
526 protected static Display mDisplay; 1034 protected static Display mDisplay;
527 1035
528 // Keep track of the surface size to normalize touch events 1036 // Keep track of the surface size to normalize touch events
529 protected static float mWidth, mHeight; 1037 protected static float mWidth, mHeight;
530 1038
531 // Startup 1039 // Startup
532 public SDLSurface(Context context) { 1040 public SDLSurface(Context context) {
533 super(context); 1041 super(context);
534 getHolder().addCallback(this); 1042 getHolder().addCallback(this);
535 1043
536 setFocusable(true); 1044 setFocusable(true);
537 setFocusableInTouchMode(true); 1045 setFocusableInTouchMode(true);
538 requestFocus(); 1046 requestFocus();
539 setOnKeyListener(this); 1047 setOnKeyListener(this);
540 setOnTouchListener(this); 1048 setOnTouchListener(this);
541 1049
542 mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); 1050 mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
543 mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); 1051 mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
544 1052
545 if(Build.VERSION.SDK_INT >= 12) { 1053 if (Build.VERSION.SDK_INT >= 12) {
546 setOnGenericMotionListener(new SDLGenericMotionListener_API12()); 1054 setOnGenericMotionListener(new SDLGenericMotionListener_API12());
547 } 1055 }
548 1056
549 // Some arbitrary defaults to avoid a potential division by zero 1057 // Some arbitrary defaults to avoid a potential division by zero
550 mWidth = 1.0f; 1058 mWidth = 1.0f;
551 mHeight = 1.0f; 1059 mHeight = 1.0f;
552 } 1060 }
553 1061
1062 public void handlePause() {
1063 enableSensor(Sensor.TYPE_ACCELEROMETER, false);
1064 }
1065
1066 public void handleResume() {
1067 setFocusable(true);
1068 setFocusableInTouchMode(true);
1069 requestFocus();
1070 setOnKeyListener(this);
1071 setOnTouchListener(this);
1072 enableSensor(Sensor.TYPE_ACCELEROMETER, true);
1073 }
1074
554 public Surface getNativeSurface() { 1075 public Surface getNativeSurface() {
555 return getHolder().getSurface(); 1076 return getHolder().getSurface();
556 } 1077 }
557 1078
558 // Called when we have a valid drawing surface 1079 // Called when we have a valid drawing surface
564 1085
565 // Called when we lose the surface 1086 // Called when we lose the surface
566 @Override 1087 @Override
567 public void surfaceDestroyed(SurfaceHolder holder) { 1088 public void surfaceDestroyed(SurfaceHolder holder) {
568 Log.v("SDL", "surfaceDestroyed()"); 1089 Log.v("SDL", "surfaceDestroyed()");
569 // Call this *before* setting mIsSurfaceReady to 'false' 1090
570 SDLActivity.handlePause(); 1091 // Transition to pause, if needed
1092 SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED;
1093 SDLActivity.handleNativeState();
1094
571 SDLActivity.mIsSurfaceReady = false; 1095 SDLActivity.mIsSurfaceReady = false;
572 SDLActivity.onNativeSurfaceDestroyed(); 1096 SDLActivity.onNativeSurfaceDestroyed();
573 } 1097 }
574 1098
575 // Called when the surface is resized 1099 // Called when the surface is resized
623 break; 1147 break;
624 } 1148 }
625 1149
626 mWidth = width; 1150 mWidth = width;
627 mHeight = height; 1151 mHeight = height;
628 SDLActivity.onNativeResize(width, height, sdlFormat); 1152 SDLActivity.onNativeResize(width, height, sdlFormat, mDisplay.getRefreshRate());
629 Log.v("SDL", "Window size:" + width + "x"+height); 1153 Log.v("SDL", "Window size: " + width + "x" + height);
630 1154
631 // Set mIsSurfaceReady to 'true' *before* making a call to handleResume 1155
1156 boolean skip = false;
1157 int requestedOrientation = SDLActivity.mSingleton.getRequestedOrientation();
1158
1159 if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)
1160 {
1161 // Accept any
1162 }
1163 else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT)
1164 {
1165 if (mWidth > mHeight) {
1166 skip = true;
1167 }
1168 } else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) {
1169 if (mWidth < mHeight) {
1170 skip = true;
1171 }
1172 }
1173
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);
1178
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;
1182 }
1183 }
1184
1185 if (skip) {
1186 Log.v("SDL", "Skip .. Surface is not ready.");
1187 SDLActivity.mIsSurfaceReady = false;
1188 return;
1189 }
1190
1191 /* Surface is ready */
632 SDLActivity.mIsSurfaceReady = true; 1192 SDLActivity.mIsSurfaceReady = true;
1193
1194 /* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */
633 SDLActivity.onNativeSurfaceChanged(); 1195 SDLActivity.onNativeSurfaceChanged();
634 1196
635 1197 SDLActivity.handleNativeState();
636 if (SDLActivity.mSDLThread == null) { 1198 }
637 // This is the entry point to the C app.
638 // Start up the C app thread and enable sensor input for the first time
639
640 SDLActivity.mSDLThread = new Thread(new SDLMain(), "SDLThread");
641 enableSensor(Sensor.TYPE_ACCELEROMETER, true);
642 SDLActivity.mSDLThread.start();
643
644 // Set up a listener thread to catch when the native thread ends
645 new Thread(new Runnable(){
646 @Override
647 public void run(){
648 try {
649 SDLActivity.mSDLThread.join();
650 }
651 catch(Exception e){}
652 finally{
653 // Native thread has finished
654 if (! SDLActivity.mExitCalledFromJava) {
655 SDLActivity.handleNativeExit();
656 }
657 }
658 }
659 }).start();
660 }
661 }
662
663 // unused
664 @Override
665 public void onDraw(Canvas canvas) {}
666
667 1199
668 // Key events 1200 // Key events
669 @Override 1201 @Override
670 public boolean onKey(View v, int keyCode, KeyEvent event) { 1202 public boolean onKey(View v, int keyCode, KeyEvent event) {
671 // Dispatch the different events depending on where they come from 1203 // Dispatch the different events depending on where they come from
672 // Some SOURCE_DPAD or SOURCE_GAMEPAD are also SOURCE_KEYBOARD 1204 // Some SOURCE_JOYSTICK, SOURCE_DPAD or SOURCE_GAMEPAD are also SOURCE_KEYBOARD
673 // So, we try to process them as DPAD or GAMEPAD events first, if that fails we try them as KEYBOARD 1205 // So, we try to process them as JOYSTICK/DPAD/GAMEPAD events first, if that fails we try them as KEYBOARD
674 1206 //
675 if ( (event.getSource() & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) { 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
676 if (event.getAction() == KeyEvent.ACTION_DOWN) { 1212 if (event.getAction() == KeyEvent.ACTION_DOWN) {
677 if (SDLActivity.onNativePadDown(event.getDeviceId(), keyCode) == 0) { 1213 if (SDLControllerManager.onNativePadDown(event.getDeviceId(), keyCode) == 0) {
678 return true; 1214 return true;
679 } 1215 }
680 } else if (event.getAction() == KeyEvent.ACTION_UP) { 1216 } else if (event.getAction() == KeyEvent.ACTION_UP) {
681 if (SDLActivity.onNativePadUp(event.getDeviceId(), keyCode) == 0) { 1217 if (SDLControllerManager.onNativePadUp(event.getDeviceId(), keyCode) == 0) {
682 return true; 1218 return true;
683 } 1219 }
684 } 1220 }
685 } 1221 }
686 1222
687 if( (event.getSource() & InputDevice.SOURCE_KEYBOARD) != 0) { 1223 if ((event.getSource() & InputDevice.SOURCE_KEYBOARD) != 0) {
688 if (event.getAction() == KeyEvent.ACTION_DOWN) { 1224 if (event.getAction() == KeyEvent.ACTION_DOWN) {
689 //Log.v("SDL", "key down: " + keyCode); 1225 //Log.v("SDL", "key down: " + keyCode);
690 SDLActivity.onNativeKeyDown(keyCode); 1226 SDLActivity.onNativeKeyDown(keyCode);
691 return true; 1227 return true;
692 } 1228 }
694 //Log.v("SDL", "key up: " + keyCode); 1230 //Log.v("SDL", "key up: " + keyCode);
695 SDLActivity.onNativeKeyUp(keyCode); 1231 SDLActivity.onNativeKeyUp(keyCode);
696 return true; 1232 return true;
697 } 1233 }
698 } 1234 }
699 1235
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;
1246 }
1247 }
1248 }
1249
700 return false; 1250 return false;
701 } 1251 }
702 1252
703 // Touch events 1253 // Touch events
704 @Override 1254 @Override
706 /* Ref: http://developer.android.com/training/gestures/multi.html */ 1256 /* Ref: http://developer.android.com/training/gestures/multi.html */
707 final int touchDevId = event.getDeviceId(); 1257 final int touchDevId = event.getDeviceId();
708 final int pointerCount = event.getPointerCount(); 1258 final int pointerCount = event.getPointerCount();
709 int action = event.getActionMasked(); 1259 int action = event.getActionMasked();
710 int pointerFingerId; 1260 int pointerFingerId;
1261 int mouseButton;
711 int i = -1; 1262 int i = -1;
712 float x,y,p; 1263 float x,y,p;
713 1264
714 switch(action) { 1265 // !!! FIXME: dump this SDK check after 2.0.4 ships and require API14.
715 case MotionEvent.ACTION_MOVE: 1266 if (event.getSource() == InputDevice.SOURCE_MOUSE && SDLActivity.mSeparateMouseAndTouch) {
716 for (i = 0; i < pointerCount; i++) { 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.
1274 }
1275 }
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;
1289 }
1290 SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
1291 }
1292 break;
1293
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();
1303 }
1304
717 pointerFingerId = event.getPointerId(i); 1305 pointerFingerId = event.getPointerId(i);
718 x = event.getX(i) / mWidth; 1306 x = event.getX(i) / mWidth;
719 y = event.getY(i) / mHeight; 1307 y = event.getY(i) / mHeight;
720 p = event.getPressure(i); 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;
1313 }
721 SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p); 1314 SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
722 } 1315 break;
723 break; 1316
724 1317 case MotionEvent.ACTION_CANCEL:
725 case MotionEvent.ACTION_UP: 1318 for (i = 0; i < pointerCount; i++) {
726 case MotionEvent.ACTION_DOWN: 1319 pointerFingerId = event.getPointerId(i);
727 // Primary pointer up/down, the index is always zero 1320 x = event.getX(i) / mWidth;
728 i = 0; 1321 y = event.getY(i) / mHeight;
729 case MotionEvent.ACTION_POINTER_UP: 1322 p = event.getPressure(i);
730 case MotionEvent.ACTION_POINTER_DOWN: 1323 if (p > 1.0f) {
731 // Non primary pointer up/down 1324 // may be larger than 1.0f on some devices
732 if (i == -1) { 1325 // see the documentation of getPressure(i)
733 i = event.getActionIndex(); 1326 p = 1.0f;
734 } 1327 }
735 1328 SDLActivity.onNativeTouch(touchDevId, pointerFingerId, MotionEvent.ACTION_UP, x, y, p);
736 pointerFingerId = event.getPointerId(i); 1329 }
737 x = event.getX(i) / mWidth; 1330 break;
738 y = event.getY(i) / mHeight; 1331
739 p = event.getPressure(i); 1332 default:
740 SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p); 1333 break;
741 break; 1334 }
742
743 default:
744 break;
745 } 1335 }
746 1336
747 return true; 1337 return true;
748 } 1338 }
749 1339
750 // Sensor events 1340 // Sensor events
751 public void enableSensor(int sensortype, boolean enabled) { 1341 public void enableSensor(int sensortype, boolean enabled) {
752 // TODO: This uses getDefaultSensor - what if we have >1 accels? 1342 // TODO: This uses getDefaultSensor - what if we have >1 accels?
753 if (enabled) { 1343 if (enabled) {
754 mSensorManager.registerListener(this, 1344 mSensorManager.registerListener(this,
755 mSensorManager.getDefaultSensor(sensortype), 1345 mSensorManager.getDefaultSensor(sensortype),
756 SensorManager.SENSOR_DELAY_GAME, null); 1346 SensorManager.SENSOR_DELAY_GAME, null);
757 } else { 1347 } else {
758 mSensorManager.unregisterListener(this, 1348 mSensorManager.unregisterListener(this,
759 mSensorManager.getDefaultSensor(sensortype)); 1349 mSensorManager.getDefaultSensor(sensortype));
760 } 1350 }
761 } 1351 }
762 1352
763 @Override 1353 @Override
764 public void onAccuracyChanged(Sensor sensor, int accuracy) { 1354 public void onAccuracyChanged(Sensor sensor, int accuracy) {
765 // TODO 1355 // TODO
766 } 1356 }
767 1357
787 y = event.values[1]; 1377 y = event.values[1];
788 break; 1378 break;
789 } 1379 }
790 SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH, 1380 SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH,
791 y / SensorManager.GRAVITY_EARTH, 1381 y / SensorManager.GRAVITY_EARTH,
792 event.values[2] / SensorManager.GRAVITY_EARTH - 1); 1382 event.values[2] / SensorManager.GRAVITY_EARTH);
793 } 1383 }
794 } 1384 }
795 } 1385 }
796 1386
797 /* This is a fake invisible editor view that receives the input and defines the 1387 /* This is a fake invisible editor view that receives the input and defines the
798 * pan&scan region 1388 * pan&scan region
799 */ 1389 */
812 return true; 1402 return true;
813 } 1403 }
814 1404
815 @Override 1405 @Override
816 public boolean onKey(View v, int keyCode, KeyEvent event) { 1406 public boolean onKey(View v, int keyCode, KeyEvent event) {
817 1407 /*
818 // This handles the hardware keyboard input 1408 * This handles the hardware keyboard input
819 if (event.isPrintingKey()) { 1409 */
820 if (event.getAction() == KeyEvent.ACTION_DOWN) { 1410 if (event.getAction() == KeyEvent.ACTION_DOWN) {
1411 if (SDLActivity.isTextInputEvent(event)) {
821 ic.commitText(String.valueOf((char) event.getUnicodeChar()), 1); 1412 ic.commitText(String.valueOf((char) event.getUnicodeChar()), 1);
822 } 1413 }
823 return true;
824 }
825
826 if (event.getAction() == KeyEvent.ACTION_DOWN) {
827 SDLActivity.onNativeKeyDown(keyCode); 1414 SDLActivity.onNativeKeyDown(keyCode);
828 return true; 1415 return true;
829 } else if (event.getAction() == KeyEvent.ACTION_UP) { 1416 } else if (event.getAction() == KeyEvent.ACTION_UP) {
830 SDLActivity.onNativeKeyUp(keyCode); 1417 SDLActivity.onNativeKeyUp(keyCode);
831 return true; 1418 return true;
832 } 1419 }
833
834 return false; 1420 return false;
835 } 1421 }
836 1422
837 // 1423 //
838 @Override 1424 @Override
839 public boolean onKeyPreIme (int keyCode, KeyEvent event) { 1425 public boolean onKeyPreIme (int keyCode, KeyEvent event) {
840 // As seen on StackOverflow: http://stackoverflow.com/questions/7634346/keyboard-hide-event 1426 // As seen on StackOverflow: http://stackoverflow.com/questions/7634346/keyboard-hide-event
841 // FIXME: Discussion at http://bugzilla.libsdl.org/show_bug.cgi?id=1639 1427 // FIXME: Discussion at http://bugzilla.libsdl.org/show_bug.cgi?id=1639
842 // FIXME: This is not a 100% effective solution to the problem of detecting if the keyboard is showing or not 1428 // FIXME: This is not a 100% effective solution to the problem of detecting if the keyboard is showing or not
843 // FIXME: A more effective solution would be to change our Layout from AbsoluteLayout to Relative or Linear 1429 // FIXME: A more effective solution would be to assume our Layout to be RelativeLayout or LinearLayout
844 // FIXME: And determine the keyboard presence doing this: http://stackoverflow.com/questions/2150078/how-to-check-visibility-of-software-keyboard-in-android 1430 // FIXME: And determine the keyboard presence doing this: http://stackoverflow.com/questions/2150078/how-to-check-visibility-of-software-keyboard-in-android
845 // 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 // FIXME: An even more effective way would be if Android provided this out of the box, but where would the fun be in that :)
846 if (event.getAction()==KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) { 1432 if (event.getAction()==KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
847 if (SDLActivity.mTextEdit != null && SDLActivity.mTextEdit.getVisibility() == View.VISIBLE) { 1433 if (SDLActivity.mTextEdit != null && SDLActivity.mTextEdit.getVisibility() == View.VISIBLE) {
848 SDLActivity.onNativeKeyboardFocusLost(); 1434 SDLActivity.onNativeKeyboardFocusLost();
853 1439
854 @Override 1440 @Override
855 public InputConnection onCreateInputConnection(EditorInfo outAttrs) { 1441 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
856 ic = new SDLInputConnection(this, true); 1442 ic = new SDLInputConnection(this, true);
857 1443
1444 outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;
858 outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI 1445 outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI
859 | 33554432 /* API 11: EditorInfo.IME_FLAG_NO_FULLSCREEN */; 1446 | EditorInfo.IME_FLAG_NO_FULLSCREEN /* API 11 */;
860 1447
861 return ic; 1448 return ic;
862 } 1449 }
863 } 1450 }
864 1451
869 1456
870 } 1457 }
871 1458
872 @Override 1459 @Override
873 public boolean sendKeyEvent(KeyEvent event) { 1460 public boolean sendKeyEvent(KeyEvent event) {
874
875 /* 1461 /*
876 * This handles the keycodes from soft keyboard (and IME-translated 1462 * This handles the keycodes from soft keyboard (and IME-translated input from hardkeyboard)
877 * input from hardkeyboard)
878 */ 1463 */
879 int keyCode = event.getKeyCode(); 1464 int keyCode = event.getKeyCode();
880 if (event.getAction() == KeyEvent.ACTION_DOWN) { 1465 if (event.getAction() == KeyEvent.ACTION_DOWN) {
881 if (event.isPrintingKey()) { 1466 if (SDLActivity.isTextInputEvent(event)) {
882 commitText(String.valueOf((char) event.getUnicodeChar()), 1); 1467 commitText(String.valueOf((char) event.getUnicodeChar()), 1);
883 } 1468 }
884 SDLActivity.onNativeKeyDown(keyCode); 1469 SDLActivity.onNativeKeyDown(keyCode);
885 return true; 1470 return true;
886 } else if (event.getAction() == KeyEvent.ACTION_UP) { 1471 } else if (event.getAction() == KeyEvent.ACTION_UP) {
887
888 SDLActivity.onNativeKeyUp(keyCode); 1472 SDLActivity.onNativeKeyUp(keyCode);
889 return true; 1473 return true;
890 } 1474 }
891 return super.sendKeyEvent(event); 1475 return super.sendKeyEvent(event);
892 } 1476 }
910 public native void nativeCommitText(String text, int newCursorPosition); 1494 public native void nativeCommitText(String text, int newCursorPosition);
911 1495
912 public native void nativeSetComposingText(String text, int newCursorPosition); 1496 public native void nativeSetComposingText(String text, int newCursorPosition);
913 1497
914 @Override 1498 @Override
915 public boolean deleteSurroundingText(int beforeLength, int afterLength) { 1499 public boolean deleteSurroundingText(int beforeLength, int afterLength) {
916 // Workaround to capture backspace key. Ref: http://stackoverflow.com/questions/14560344/android-backspace-in-webview-baseinputconnection 1500 // Workaround to capture backspace key. Ref: http://stackoverflow.com/questions/14560344/android-backspace-in-webview-baseinputconnection
917 if (beforeLength == 1 && afterLength == 0) { 1501 // and https://bugzilla.libsdl.org/show_bug.cgi?id=2265
918 // backspace 1502 if (beforeLength > 0 && afterLength == 0) {
919 return super.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)) 1503 boolean ret = true;
920 && super.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL)); 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;
1509 }
1510 return ret;
921 } 1511 }
922 1512
923 return super.deleteSurroundingText(beforeLength, afterLength); 1513 return super.deleteSurroundingText(beforeLength, afterLength);
924 } 1514 }
925 } 1515 }
926 1516
927 /* A null joystick handler for API level < 12 devices (the accelerometer is handled separately) */ 1517 interface SDLClipboardHandler {
928 class SDLJoystickHandler { 1518
1519 public boolean clipboardHasText();
1520 public String clipboardGetText();
1521 public void clipboardSetText(String string);
1522
1523 }
1524
1525
1526 class SDLClipboardHandler_API11 implements
1527 SDLClipboardHandler,
1528 android.content.ClipboardManager.OnPrimaryClipChangedListener {
1529
1530 protected android.content.ClipboardManager mClipMgr;
1531
1532 SDLClipboardHandler_API11() {
1533 mClipMgr = (android.content.ClipboardManager) SDL.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
1534 mClipMgr.addPrimaryClipChangedListener(this);
1535 }
1536
1537 @Override
1538 public boolean clipboardHasText() {
1539 return mClipMgr.hasText();
1540 }
1541
1542 @Override
1543 public String clipboardGetText() {
1544 CharSequence text;
1545 text = mClipMgr.getText();
1546 if (text != null) {
1547 return text.toString();
1548 }
1549 return null;
1550 }
1551
1552 @Override
1553 public void clipboardSetText(String string) {
1554 mClipMgr.removePrimaryClipChangedListener(this);
1555 mClipMgr.setText(string);
1556 mClipMgr.addPrimaryClipChangedListener(this);
1557 }
929 1558
930 public boolean handleMotionEvent(MotionEvent event) { 1559 @Override
931 return false; 1560 public void onPrimaryClipChanged() {
932 } 1561 SDLActivity.onNativeClipboardChanged();
933 1562 }
934 public void pollInputDevices() { 1563
935 }
936 } 1564 }
937 1565
938 /* Actual joystick functionality available for API >= 12 devices */ 1566 class SDLClipboardHandler_Old implements
939 class SDLJoystickHandler_API12 extends SDLJoystickHandler { 1567 SDLClipboardHandler {
1568
1569 protected android.text.ClipboardManager mClipMgrOld;
940 1570
941 class SDLJoystick { 1571 SDLClipboardHandler_Old() {
942 public int device_id; 1572 mClipMgrOld = (android.text.ClipboardManager) SDL.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
943 public String name; 1573 }
944 public ArrayList<InputDevice.MotionRange> axes; 1574
945 public ArrayList<InputDevice.MotionRange> hats; 1575 @Override
946 } 1576 public boolean clipboardHasText() {
947 class RangeComparator implements Comparator<InputDevice.MotionRange> 1577 return mClipMgrOld.hasText();
948 { 1578 }
949 @Override 1579
950 public int compare(InputDevice.MotionRange arg0, InputDevice.MotionRange arg1) { 1580 @Override
951 return arg0.getAxis() - arg1.getAxis(); 1581 public String clipboardGetText() {
952 } 1582 CharSequence text;
953 } 1583 text = mClipMgrOld.getText();
954 1584 if (text != null) {
955 private ArrayList<SDLJoystick> mJoysticks; 1585 return text.toString();
956 1586 }
957 public SDLJoystickHandler_API12() { 1587 return null;
958 1588 }
959 mJoysticks = new ArrayList<SDLJoystick>(); 1589
960 } 1590 @Override
961 1591 public void clipboardSetText(String string) {
962 @Override 1592 mClipMgrOld.setText(string);
963 public void pollInputDevices() { 1593 }
964 int[] deviceIds = InputDevice.getDeviceIds();
965 // It helps processing the device ids in reverse order
966 // For example, in the case of the XBox 360 wireless dongle,
967 // so the first controller seen by SDL matches what the receiver
968 // considers to be the first controller
969
970 for(int i=deviceIds.length-1; i>-1; i--) {
971 SDLJoystick joystick = getJoystick(deviceIds[i]);
972 if (joystick == null) {
973 joystick = new SDLJoystick();
974 InputDevice joystickDevice = InputDevice.getDevice(deviceIds[i]);
975 if( (joystickDevice.getSources() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) {
976 joystick.device_id = deviceIds[i];
977 joystick.name = joystickDevice.getName();
978 joystick.axes = new ArrayList<InputDevice.MotionRange>();
979 joystick.hats = new ArrayList<InputDevice.MotionRange>();
980
981 List<InputDevice.MotionRange> ranges = joystickDevice.getMotionRanges();
982 Collections.sort(ranges, new RangeComparator());
983 for (InputDevice.MotionRange range : ranges ) {
984 if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0 ) {
985 if (range.getAxis() == MotionEvent.AXIS_HAT_X ||
986 range.getAxis() == MotionEvent.AXIS_HAT_Y) {
987 joystick.hats.add(range);
988 }
989 else {
990 joystick.axes.add(range);
991 }
992 }
993 }
994
995 mJoysticks.add(joystick);
996 SDLActivity.nativeAddJoystick(joystick.device_id, joystick.name, 0, -1,
997 joystick.axes.size(), joystick.hats.size()/2, 0);
998 }
999 }
1000 }
1001
1002 /* Check removed devices */
1003 ArrayList<Integer> removedDevices = new ArrayList<Integer>();
1004 for(int i=0; i < mJoysticks.size(); i++) {
1005 int device_id = mJoysticks.get(i).device_id;
1006 int j;
1007 for (j=0; j < deviceIds.length; j++) {
1008 if (device_id == deviceIds[j]) break;
1009 }
1010 if (j == deviceIds.length) {
1011 removedDevices.add(device_id);
1012 }
1013 }
1014
1015 for(int i=0; i < removedDevices.size(); i++) {
1016 int device_id = removedDevices.get(i);
1017 SDLActivity.nativeRemoveJoystick(device_id);
1018 for (int j=0; j < mJoysticks.size(); j++) {
1019 if (mJoysticks.get(j).device_id == device_id) {
1020 mJoysticks.remove(j);
1021 break;
1022 }
1023 }
1024 }
1025 }
1026
1027 protected SDLJoystick getJoystick(int device_id) {
1028 for(int i=0; i < mJoysticks.size(); i++) {
1029 if (mJoysticks.get(i).device_id == device_id) {
1030 return mJoysticks.get(i);
1031 }
1032 }
1033 return null;
1034 }
1035
1036 @Override
1037 public boolean handleMotionEvent(MotionEvent event) {
1038 if ( (event.getSource() & InputDevice.SOURCE_JOYSTICK) != 0) {
1039 int actionPointerIndex = event.getActionIndex();
1040 int action = event.getActionMasked();
1041 switch(action) {
1042 case MotionEvent.ACTION_MOVE:
1043 SDLJoystick joystick = getJoystick(event.getDeviceId());
1044 if ( joystick != null ) {
1045 for (int i = 0; i < joystick.axes.size(); i++) {
1046 InputDevice.MotionRange range = joystick.axes.get(i);
1047 /* Normalize the value to -1...1 */
1048 float value = ( event.getAxisValue( range.getAxis(), actionPointerIndex) - range.getMin() ) / range.getRange() * 2.0f - 1.0f;
1049 SDLActivity.onNativeJoy(joystick.device_id, i, value );
1050 }
1051 for (int i = 0; i < joystick.hats.size(); i+=2) {
1052 int hatX = Math.round(event.getAxisValue( joystick.hats.get(i).getAxis(), actionPointerIndex ) );
1053 int hatY = Math.round(event.getAxisValue( joystick.hats.get(i+1).getAxis(), actionPointerIndex ) );
1054 SDLActivity.onNativeHat(joystick.device_id, i/2, hatX, hatY );
1055 }
1056 }
1057 break;
1058 default:
1059 break;
1060 }
1061 }
1062 return true;
1063 }
1064 } 1594 }
1065 1595
1066 class SDLGenericMotionListener_API12 implements View.OnGenericMotionListener {
1067 // Generic Motion (mouse hover, joystick...) events go here
1068 // We only have joysticks yet
1069 @Override
1070 public boolean onGenericMotion(View v, MotionEvent event) {
1071 return SDLActivity.handleJoystickMotionEvent(event);
1072 }
1073 }