Mercurial > repos > blastem
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 } |