1 package org.libsdl.app; 2 3 import java.io.IOException; 4 import java.io.InputStream; 5 import java.util.Arrays; 6 import java.util.Hashtable; 7 import java.lang.reflect.Method; 8 import java.lang.Math; 9 10 import android.app.*; 11 import android.content.*; 12 import android.content.res.Configuration; 13 import android.text.InputType; 14 import android.view.*; 15 import android.view.inputmethod.BaseInputConnection; 16 import android.view.inputmethod.EditorInfo; 17 import android.view.inputmethod.InputConnection; 18 import android.view.inputmethod.InputMethodManager; 19 import android.widget.RelativeLayout; 20 import android.widget.Button; 21 import android.widget.LinearLayout; 22 import android.widget.TextView; 23 import android.os.*; 24 import android.preference.PreferenceManager; 25 import android.util.DisplayMetrics; 26 import android.util.Log; 27 import android.util.SparseArray; 28 import android.graphics.*; 29 import android.graphics.drawable.Drawable; 30 import android.hardware.*; 31 import android.content.pm.ActivityInfo; 32 import android.content.pm.PackageManager; 33 import android.content.pm.ApplicationInfo; 34 35 /** 36 SDL Activity 37 */ 38 public class SDLActivity extends Activity implements View.OnSystemUiVisibilityChangeListener { 39 private static final String TAG = "SDL"; 40 41 public static boolean mIsResumedCalled, mHasFocus; 42 public static final boolean mHasMultiWindow = (Build.VERSION.SDK_INT >= 24); 43 public static boolean mAllowSDLOrientationChanges = false; 44 45 // Cursor types 46 private static final int SDL_SYSTEM_CURSOR_NONE = -1; 47 private static final int SDL_SYSTEM_CURSOR_ARROW = 0; 48 private static final int SDL_SYSTEM_CURSOR_IBEAM = 1; 49 private static final int SDL_SYSTEM_CURSOR_WAIT = 2; 50 private static final int SDL_SYSTEM_CURSOR_CROSSHAIR = 3; 51 private static final int SDL_SYSTEM_CURSOR_WAITARROW = 4; 52 private static final int SDL_SYSTEM_CURSOR_SIZENWSE = 5; 53 private static final int SDL_SYSTEM_CURSOR_SIZENESW = 6; 54 private static final int SDL_SYSTEM_CURSOR_SIZEWE = 7; 55 private static final int SDL_SYSTEM_CURSOR_SIZENS = 8; 56 private static final int SDL_SYSTEM_CURSOR_SIZEALL = 9; 57 private static final int SDL_SYSTEM_CURSOR_NO = 10; 58 private static final int SDL_SYSTEM_CURSOR_HAND = 11; 59 60 protected static final int SDL_ORIENTATION_UNKNOWN = 0; 61 protected static final int SDL_ORIENTATION_LANDSCAPE = 1; 62 protected static final int SDL_ORIENTATION_LANDSCAPE_FLIPPED = 2; 63 protected static final int SDL_ORIENTATION_PORTRAIT = 3; 64 protected static final int SDL_ORIENTATION_PORTRAIT_FLIPPED = 4; 65 66 protected static int mCurrentOrientation; 67 68 // Handle the state of the native layer 69 public enum NativeState { 70 INIT, RESUMED, PAUSED 71 } 72 73 public static NativeState mNextNativeState; 74 public static NativeState mCurrentNativeState; 75 76 /** If shared libraries (e.g. SDL or the native application) could not be loaded. */ 77 public static boolean mBrokenLibraries; 78 79 // Main components 80 protected static SDLActivity mSingleton; 81 protected static SDLSurface mSurface; 82 protected static View mTextEdit; 83 protected static boolean mScreenKeyboardShown; 84 protected static ViewGroup mLayout; 85 protected static SDLClipboardHandler mClipboardHandler; 86 protected static Hashtable<Integer, PointerIcon> mCursors; 87 protected static int mLastCursorID; 88 protected static SDLGenericMotionListener_API12 mMotionListener; 89 protected static HIDDeviceManager mHIDDeviceManager; 90 91 // This is what SDL runs in. It invokes SDL_main(), eventually 92 protected static Thread mSDLThread; 93 getMotionListener()94 protected static SDLGenericMotionListener_API12 getMotionListener() { 95 if (mMotionListener == null) { 96 if (Build.VERSION.SDK_INT >= 26) { 97 mMotionListener = new SDLGenericMotionListener_API26(); 98 } else 99 if (Build.VERSION.SDK_INT >= 24) { 100 mMotionListener = new SDLGenericMotionListener_API24(); 101 } else { 102 mMotionListener = new SDLGenericMotionListener_API12(); 103 } 104 } 105 106 return mMotionListener; 107 } 108 109 /** 110 * This method returns the name of the shared object with the application entry point 111 * It can be overridden by derived classes. 112 */ getMainSharedObject()113 protected String getMainSharedObject() { 114 String library; 115 String[] libraries = SDLActivity.mSingleton.getLibraries(); 116 if (libraries.length > 0) { 117 library = "lib" + libraries[libraries.length - 1] + ".so"; 118 } else { 119 library = "libmain.so"; 120 } 121 return getContext().getApplicationInfo().nativeLibraryDir + "/" + library; 122 } 123 124 /** 125 * This method returns the name of the application entry point 126 * It can be overridden by derived classes. 127 */ getMainFunction()128 protected String getMainFunction() { 129 return "SDL_main"; 130 } 131 132 /** 133 * This method is called by SDL before loading the native shared libraries. 134 * It can be overridden to provide names of shared libraries to be loaded. 135 * The default implementation returns the defaults. It never returns null. 136 * An array returned by a new implementation must at least contain "SDL2". 137 * Also keep in mind that the order the libraries are loaded may matter. 138 * @return names of shared libraries to be loaded (e.g. "SDL2", "main"). 139 */ getLibraries()140 protected String[] getLibraries() { 141 return new String[] { 142 "c++_shared", 143 "hidapi", 144 "SDL2", 145 "SDL2_image", 146 "SDL2_mixer", 147 // "SDL2_net", 148 "SDL2_ttf", 149 "intl-lite", 150 "main" 151 }; 152 } 153 154 // Load the .so loadLibraries()155 public void loadLibraries() { 156 for (String lib : getLibraries()) { 157 SDL.loadLibrary(lib); 158 } 159 } 160 161 /** 162 * This method is called by SDL before starting the native application thread. 163 * It can be overridden to provide the arguments after the application name. 164 * The default implementation returns an empty array. It never returns null. 165 * @return arguments for the native application. 166 */ getArguments()167 protected String[] getArguments() { 168 return new String[0]; 169 } 170 initialize()171 public static void initialize() { 172 // The static nature of the singleton and Android quirkyness force us to initialize everything here 173 // Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values 174 mSingleton = null; 175 mSurface = null; 176 mTextEdit = null; 177 mLayout = null; 178 mClipboardHandler = null; 179 mCursors = new Hashtable<Integer, PointerIcon>(); 180 mLastCursorID = 0; 181 mSDLThread = null; 182 mBrokenLibraries = false; 183 mIsResumedCalled = false; 184 mHasFocus = true; 185 mNextNativeState = NativeState.INIT; 186 mCurrentNativeState = NativeState.INIT; 187 } 188 189 // Setup 190 @Override onCreate(Bundle savedInstanceState)191 protected void onCreate(Bundle savedInstanceState) { 192 Log.v(TAG, "Device: " + Build.DEVICE); 193 Log.v(TAG, "Model: " + Build.MODEL); 194 Log.v(TAG, "onCreate()"); 195 super.onCreate(savedInstanceState); 196 197 try { 198 Thread.currentThread().setName("SDLActivity"); 199 } catch (Exception e) { 200 Log.v(TAG, "modify thread properties failed " + e.toString()); 201 } 202 203 // Load shared libraries 204 String errorMsgBrokenLib = ""; 205 try { 206 loadLibraries(); 207 } catch(UnsatisfiedLinkError e) { 208 System.err.println(e.getMessage()); 209 mBrokenLibraries = true; 210 errorMsgBrokenLib = e.getMessage(); 211 } catch(Exception e) { 212 System.err.println(e.getMessage()); 213 mBrokenLibraries = true; 214 errorMsgBrokenLib = e.getMessage(); 215 } 216 217 if (mBrokenLibraries) 218 { 219 mSingleton = this; 220 AlertDialog.Builder dlgAlert = new AlertDialog.Builder(this); 221 dlgAlert.setMessage("An error occurred while trying to start the application. Please try again and/or reinstall." 222 + System.getProperty("line.separator") 223 + System.getProperty("line.separator") 224 + "Error: " + errorMsgBrokenLib); 225 dlgAlert.setTitle("SDL Error"); 226 dlgAlert.setPositiveButton("Exit", 227 new DialogInterface.OnClickListener() { 228 @Override 229 public void onClick(DialogInterface dialog,int id) { 230 // if this button is clicked, close current activity 231 SDLActivity.mSingleton.finish(); 232 } 233 }); 234 dlgAlert.setCancelable(false); 235 dlgAlert.create().show(); 236 237 return; 238 } 239 240 // Set up JNI 241 SDL.setupJNI(); 242 243 // Initialize state 244 SDL.initialize(); 245 246 // So we can call stuff from static callbacks 247 mSingleton = this; 248 SDL.setContext(this); 249 250 mClipboardHandler = new SDLClipboardHandler_API11(); 251 252 mHIDDeviceManager = HIDDeviceManager.acquire(this); 253 254 // Set up the surface 255 mSurface = new SDLSurface(getApplication()); 256 257 mLayout = new RelativeLayout(this); 258 mLayout.addView(mSurface); 259 mLayout.setVisibility(View.INVISIBLE); // we will make this visible later through C++ call -> Java 260 setContentView(mLayout); 261 262 // Get our current screen orientation and pass it down. 263 mCurrentOrientation = SDLActivity.getCurrentOrientation(); 264 // Only record current orientation 265 SDLActivity.onNativeOrientationChanged(mCurrentOrientation); 266 267 setContentView(mLayout); 268 269 setWindowStyle(false); 270 271 mLayout.getViewTreeObserver().addOnGlobalLayoutListener( 272 new ViewTreeObserver.OnGlobalLayoutListener() { 273 public void onGlobalLayout(){ 274 Rect r = new Rect(); 275 View view = getWindow().getDecorView(); 276 view.getWindowVisibleDisplayFrame(r); 277 //Log.v(TAG, "getWindowVisibleDisplayFrame(r): r.left " + r.left + " r.top " + r.top + " r.right " + r.right + " r.bottom " + r.bottom); 278 SDLActivity.onNativeVisibleDisplayFrameChanged(r.left, r.top, r.right, r.bottom); 279 } 280 }); 281 282 getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(this); 283 284 // Get filename from "Open with" of another application 285 Intent intent = getIntent(); 286 if (intent != null && intent.getData() != null) { 287 String filename = intent.getData().getPath(); 288 if (filename != null) { 289 Log.v(TAG, "Got filename: " + filename); 290 SDLActivity.onNativeDropFile(filename); 291 } 292 } 293 } 294 pauseNativeThread()295 protected void pauseNativeThread() { 296 mNextNativeState = NativeState.PAUSED; 297 mIsResumedCalled = false; 298 299 if (SDLActivity.mBrokenLibraries) { 300 return; 301 } 302 303 SDLActivity.handleNativeState(); 304 } 305 resumeNativeThread()306 protected void resumeNativeThread() { 307 mNextNativeState = NativeState.RESUMED; 308 mIsResumedCalled = true; 309 310 if (SDLActivity.mBrokenLibraries) { 311 return; 312 } 313 314 SDLActivity.handleNativeState(); 315 } 316 317 // Events 318 @Override onPause()319 protected void onPause() { 320 Log.v(TAG, "onPause()"); 321 super.onPause(); 322 323 if (mHIDDeviceManager != null) { 324 mHIDDeviceManager.setFrozen(true); 325 } 326 if (!mHasMultiWindow) { 327 pauseNativeThread(); 328 } 329 } 330 331 @Override onResume()332 protected void onResume() { 333 Log.v(TAG, "onResume()"); 334 super.onResume(); 335 336 if (mHIDDeviceManager != null) { 337 mHIDDeviceManager.setFrozen(false); 338 } 339 if (!mHasMultiWindow) { 340 resumeNativeThread(); 341 } 342 } 343 344 @Override onStop()345 protected void onStop() { 346 Log.v(TAG, "onStop()"); 347 super.onStop(); 348 if (mHasMultiWindow) { 349 pauseNativeThread(); 350 } 351 } 352 353 @Override onStart()354 protected void onStart() { 355 Log.v(TAG, "onStart()"); 356 super.onStart(); 357 if (mHasMultiWindow) { 358 resumeNativeThread(); 359 } 360 } 361 getCurrentOrientation()362 public static int getCurrentOrientation() { 363 final Context context = SDLActivity.getContext(); 364 final Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); 365 366 int result = SDL_ORIENTATION_UNKNOWN; 367 368 switch (display.getRotation()) { 369 case Surface.ROTATION_0: 370 result = SDL_ORIENTATION_PORTRAIT; 371 break; 372 373 case Surface.ROTATION_90: 374 result = SDL_ORIENTATION_LANDSCAPE; 375 break; 376 377 case Surface.ROTATION_180: 378 result = SDL_ORIENTATION_PORTRAIT_FLIPPED; 379 break; 380 381 case Surface.ROTATION_270: 382 result = SDL_ORIENTATION_LANDSCAPE_FLIPPED; 383 break; 384 } 385 386 return result; 387 } 388 389 @Override onWindowFocusChanged(boolean hasFocus)390 public void onWindowFocusChanged(boolean hasFocus) { 391 super.onWindowFocusChanged(hasFocus); 392 Log.v(TAG, "onWindowFocusChanged(): " + hasFocus); 393 394 if (SDLActivity.mBrokenLibraries) { 395 return; 396 } 397 398 mHasFocus = hasFocus; 399 if (hasFocus) { 400 mNextNativeState = NativeState.RESUMED; 401 SDLActivity.getMotionListener().reclaimRelativeMouseModeIfNeeded(); 402 403 SDLActivity.handleNativeState(); 404 nativeFocusChanged(true); 405 406 } else { 407 nativeFocusChanged(false); 408 if (!mHasMultiWindow) { 409 mNextNativeState = NativeState.PAUSED; 410 SDLActivity.handleNativeState(); 411 } 412 } 413 } 414 415 @Override onLowMemory()416 public void onLowMemory() { 417 Log.v(TAG, "onLowMemory()"); 418 super.onLowMemory(); 419 420 if (SDLActivity.mBrokenLibraries) { 421 return; 422 } 423 424 SDLActivity.nativeLowMemory(); 425 } 426 427 @Override onDestroy()428 protected void onDestroy() { 429 Log.v(TAG, "onDestroy()"); 430 431 if (mHIDDeviceManager != null) { 432 HIDDeviceManager.release(mHIDDeviceManager); 433 mHIDDeviceManager = null; 434 } 435 436 if (SDLActivity.mBrokenLibraries) { 437 super.onDestroy(); 438 return; 439 } 440 441 if (SDLActivity.mSDLThread != null) { 442 443 // Send Quit event to "SDLThread" thread 444 SDLActivity.nativeSendQuit(); 445 446 // Wait for "SDLThread" thread to end 447 try { 448 SDLActivity.mSDLThread.join(); 449 } catch(Exception e) { 450 Log.v(TAG, "Problem stopping SDLThread: " + e); 451 } 452 } 453 454 SDLActivity.nativeQuit(); 455 456 super.onDestroy(); 457 } 458 459 @Override onBackPressed()460 public void onBackPressed() { 461 // Check if we want to block the back button in case of mouse right click. 462 // 463 // If we do, the normal hardware back button will no longer work and people have to use home, 464 // but the mouse right click will work. 465 // 466 String trapBack = SDLActivity.nativeGetHint("SDL_ANDROID_TRAP_BACK_BUTTON"); 467 if ((trapBack != null) && trapBack.equals("1")) { 468 // Exit and let the mouse handler handle this button (if appropriate) 469 return; 470 } 471 472 // Default system back button behavior. 473 if (!isFinishing()) { 474 super.onBackPressed(); 475 } 476 } 477 478 // Called by JNI from SDL. manualBackButton()479 public static void manualBackButton() { 480 mSingleton.pressBackButton(); 481 } 482 483 // Used to get us onto the activity's main thread pressBackButton()484 public void pressBackButton() { 485 runOnUiThread(new Runnable() { 486 @Override 487 public void run() { 488 if (!SDLActivity.this.isFinishing()) { 489 SDLActivity.this.superOnBackPressed(); 490 } 491 } 492 }); 493 } 494 495 // Used to access the system back behavior. superOnBackPressed()496 public void superOnBackPressed() { 497 super.onBackPressed(); 498 } 499 500 @Override dispatchKeyEvent(KeyEvent event)501 public boolean dispatchKeyEvent(KeyEvent event) { 502 503 if (SDLActivity.mBrokenLibraries) { 504 return false; 505 } 506 507 int keyCode = event.getKeyCode(); 508 // Ignore certain special keys so they're handled by Android 509 if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || 510 keyCode == KeyEvent.KEYCODE_VOLUME_UP || 511 keyCode == KeyEvent.KEYCODE_CAMERA || 512 keyCode == KeyEvent.KEYCODE_ZOOM_IN || /* API 11 */ 513 keyCode == KeyEvent.KEYCODE_ZOOM_OUT /* API 11 */ 514 ) { 515 return false; 516 } 517 return super.dispatchKeyEvent(event); 518 } 519 520 /* Transition to next state */ handleNativeState()521 public static void handleNativeState() { 522 523 if (mNextNativeState == mCurrentNativeState) { 524 // Already in same state, discard. 525 return; 526 } 527 528 // Try a transition to init state 529 if (mNextNativeState == NativeState.INIT) { 530 531 mCurrentNativeState = mNextNativeState; 532 return; 533 } 534 535 // Try a transition to paused state 536 if (mNextNativeState == NativeState.PAUSED) { 537 if (mSDLThread != null) { 538 nativePause(); 539 } 540 if (mSurface != null) { 541 mSurface.handlePause(); 542 } 543 mCurrentNativeState = mNextNativeState; 544 return; 545 } 546 547 // Try a transition to resumed state 548 if (mNextNativeState == NativeState.RESUMED) { 549 if (mSurface.mIsSurfaceReady && mHasFocus && mIsResumedCalled) { 550 if (mSDLThread == null) { 551 // This is the entry point to the C app. 552 // Start up the C app thread and enable sensor input for the first time 553 // FIXME: Why aren't we enabling sensor input at start? 554 555 mSDLThread = new Thread(new SDLMain(), "SDLThread"); 556 mSurface.enableSensor(Sensor.TYPE_ACCELEROMETER, true); 557 mSDLThread.start(); 558 559 // No nativeResume(), don't signal Android_ResumeSem 560 mSurface.handleResume(); 561 } else { 562 nativeResume(); 563 mSurface.handleResume(); 564 } 565 566 mCurrentNativeState = mNextNativeState; 567 } 568 } 569 } 570 571 // Messages from the SDLMain thread 572 static final int COMMAND_CHANGE_TITLE = 1; 573 static final int COMMAND_CHANGE_WINDOW_STYLE = 2; 574 static final int COMMAND_TEXTEDIT_HIDE = 3; 575 static final int COMMAND_CHANGE_SURFACEVIEW_FORMAT = 4; 576 static final int COMMAND_SET_KEEP_SCREEN_ON = 5; 577 578 protected static final int COMMAND_USER = 0x8000; 579 580 protected static boolean mFullscreenModeActive; 581 582 /** 583 * This method is called by SDL if SDL did not handle a message itself. 584 * This happens if a received message contains an unsupported command. 585 * Method can be overwritten to handle Messages in a different class. 586 * @param command the command of the message. 587 * @param param the parameter of the message. May be null. 588 * @return if the message was handled in overridden method. 589 */ onUnhandledMessage(int command, Object param)590 protected boolean onUnhandledMessage(int command, Object param) { 591 return false; 592 } 593 594 /** 595 * A Handler class for Messages from native SDL applications. 596 * It uses current Activities as target (e.g. for the title). 597 * static to prevent implicit references to enclosing object. 598 */ 599 protected static class SDLCommandHandler extends Handler { 600 @Override handleMessage(Message msg)601 public void handleMessage(Message msg) { 602 Context context = SDL.getContext(); 603 if (context == null) { 604 Log.e(TAG, "error handling message, getContext() returned null"); 605 return; 606 } 607 switch (msg.arg1) { 608 case COMMAND_CHANGE_TITLE: 609 if (context instanceof Activity) { 610 ((Activity) context).setTitle((String)msg.obj); 611 } else { 612 Log.e(TAG, "error handling message, getContext() returned no Activity"); 613 } 614 break; 615 case COMMAND_CHANGE_WINDOW_STYLE: 616 if (Build.VERSION.SDK_INT < 19) { 617 // This version of Android doesn't support the immersive fullscreen mode 618 break; 619 } 620 if (context instanceof Activity) { 621 Window window = ((Activity) context).getWindow(); 622 if (window != null) { 623 if ((msg.obj instanceof Integer) && (((Integer) msg.obj).intValue() != 0)) { 624 int flags = View.SYSTEM_UI_FLAG_FULLSCREEN | 625 View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | 626 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | 627 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | 628 View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | 629 View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.INVISIBLE; 630 window.getDecorView().setSystemUiVisibility(flags); 631 window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); 632 window.clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); 633 SDLActivity.mFullscreenModeActive = true; 634 } else { 635 int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_VISIBLE; 636 window.getDecorView().setSystemUiVisibility(flags); 637 Context appContext = context.getApplicationContext(); 638 boolean forceFullScreen = PreferenceManager.getDefaultSharedPreferences(appContext).getBoolean("Force fullscreen", false); 639 window.addFlags(forceFullScreen ? WindowManager.LayoutParams.FLAG_FULLSCREEN : WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); 640 window.clearFlags(forceFullScreen ? WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN : WindowManager.LayoutParams.FLAG_FULLSCREEN); 641 SDLActivity.mFullscreenModeActive = forceFullScreen; 642 } 643 } 644 } else { 645 Log.e(TAG, "error handling message, getContext() returned no Activity"); 646 } 647 break; 648 case COMMAND_TEXTEDIT_HIDE: 649 if (mTextEdit != null) { 650 // Note: On some devices setting view to GONE creates a flicker in landscape. 651 // Setting the View's sizes to 0 is similar to GONE but without the flicker. 652 // The sizes will be set to useful values when the keyboard is shown again. 653 mTextEdit.setLayoutParams(new RelativeLayout.LayoutParams(0, 0)); 654 655 InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); 656 imm.hideSoftInputFromWindow(mTextEdit.getWindowToken(), 0); 657 658 mScreenKeyboardShown = false; 659 660 mSurface.requestFocus(); 661 } 662 break; 663 case COMMAND_SET_KEEP_SCREEN_ON: 664 { 665 if (context instanceof Activity) { 666 Window window = ((Activity) context).getWindow(); 667 if (window != null) { 668 if ((msg.obj instanceof Integer) && (((Integer) msg.obj).intValue() != 0)) { 669 window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 670 } else { 671 window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 672 } 673 } 674 } 675 break; 676 } 677 case COMMAND_CHANGE_SURFACEVIEW_FORMAT: 678 { 679 int format = (Integer) msg.obj; 680 int pf; 681 682 if (SDLActivity.mSurface == null) { 683 return; 684 } 685 686 SurfaceHolder holder = SDLActivity.mSurface.getHolder(); 687 if (holder == null) { 688 return; 689 } 690 691 if (format == 1) { 692 pf = PixelFormat.RGBA_8888; 693 } else if (format == 2) { 694 pf = PixelFormat.RGBX_8888; 695 } else { 696 pf = PixelFormat.RGB_565; 697 } 698 699 holder.setFormat(pf); 700 701 break; 702 } 703 default: 704 if ((context instanceof SDLActivity) && !((SDLActivity) context).onUnhandledMessage(msg.arg1, msg.obj)) { 705 Log.e(TAG, "error handling message, command is " + msg.arg1); 706 } 707 } 708 } 709 } 710 711 // Handler for the messages 712 Handler commandHandler = new SDLCommandHandler(); 713 714 // Send a message from the SDLMain thread sendCommand(int command, Object data)715 boolean sendCommand(int command, Object data) { 716 Message msg = commandHandler.obtainMessage(); 717 msg.arg1 = command; 718 msg.obj = data; 719 boolean result = commandHandler.sendMessage(msg); 720 721 if ((Build.VERSION.SDK_INT >= 19) && (command == COMMAND_CHANGE_WINDOW_STYLE)) { 722 // Ensure we don't return until the resize has actually happened, 723 // or 500ms have passed. 724 725 boolean bShouldWait = false; 726 727 if (data instanceof Integer) { 728 // Let's figure out if we're already laid out fullscreen or not. 729 Display display = ((WindowManager)getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); 730 android.util.DisplayMetrics realMetrics = new android.util.DisplayMetrics(); 731 display.getRealMetrics( realMetrics ); 732 733 boolean bFullscreenLayout = ((realMetrics.widthPixels == mSurface.getWidth()) && 734 (realMetrics.heightPixels == mSurface.getHeight())); 735 736 if (((Integer)data).intValue() == 1) { 737 // If we aren't laid out fullscreen or actively in fullscreen mode already, we're going 738 // to change size and should wait for surfaceChanged() before we return, so the size 739 // is right back in native code. If we're already laid out fullscreen, though, we're 740 // not going to change size even if we change decor modes, so we shouldn't wait for 741 // surfaceChanged() -- which may not even happen -- and should return immediately. 742 bShouldWait = !bFullscreenLayout; 743 } 744 else { 745 // If we're laid out fullscreen (even if the status bar and nav bar are present), 746 // or are actively in fullscreen, we're going to change size and should wait for 747 // surfaceChanged before we return, so the size is right back in native code. 748 bShouldWait = bFullscreenLayout; 749 } 750 } 751 752 if (bShouldWait && (SDLActivity.getContext() != null)) { 753 // We'll wait for the surfaceChanged() method, which will notify us 754 // when called. That way, we know our current size is really the 755 // size we need, instead of grabbing a size that's still got 756 // the navigation and/or status bars before they're hidden. 757 // 758 // We'll wait for up to half a second, because some devices 759 // take a surprisingly long time for the surface resize, but 760 // then we'll just give up and return. 761 // 762 synchronized(SDLActivity.getContext()) { 763 try { 764 SDLActivity.getContext().wait(500); 765 } 766 catch (InterruptedException ie) { 767 ie.printStackTrace(); 768 } 769 } 770 } 771 } 772 773 return result; 774 } 775 776 // C functions we call nativeSetupJNI()777 public static native int nativeSetupJNI(); nativeRunMain(String library, String function, Object arguments)778 public static native int nativeRunMain(String library, String function, Object arguments); nativeLowMemory()779 public static native void nativeLowMemory(); nativeSendQuit()780 public static native void nativeSendQuit(); nativeQuit()781 public static native void nativeQuit(); nativePause()782 public static native void nativePause(); nativeResume()783 public static native void nativeResume(); nativeFocusChanged(boolean hasFocus)784 public static native void nativeFocusChanged(boolean hasFocus); onNativeDropFile(String filename)785 public static native void onNativeDropFile(String filename); onNativeVisibleDisplayFrameChanged(int left, int top, int right, int bottom)786 public static native void onNativeVisibleDisplayFrameChanged(int left, int top, int right, int bottom); nativeSetScreenResolution(int surfaceWidth, int surfaceHeight, int deviceWidth, int deviceHeight, int format, float rate)787 public static native void nativeSetScreenResolution(int surfaceWidth, int surfaceHeight, int deviceWidth, int deviceHeight, int format, float rate); onNativeResize()788 public static native void onNativeResize(); onNativeKeyDown(int keycode)789 public static native void onNativeKeyDown(int keycode); onNativeKeyUp(int keycode)790 public static native void onNativeKeyUp(int keycode); onNativeSoftReturnKey()791 public static native boolean onNativeSoftReturnKey(); onNativeKeyboardFocusLost()792 public static native void onNativeKeyboardFocusLost(); onNativeMouse(int button, int action, float x, float y, boolean relative)793 public static native void onNativeMouse(int button, int action, float x, float y, boolean relative); onNativeTouch(int touchDevId, int pointerFingerId, int action, float x, float y, float p)794 public static native void onNativeTouch(int touchDevId, int pointerFingerId, 795 int action, float x, 796 float y, float p); onNativeAccel(float x, float y, float z)797 public static native void onNativeAccel(float x, float y, float z); onNativeClipboardChanged()798 public static native void onNativeClipboardChanged(); onNativeSurfaceCreated()799 public static native void onNativeSurfaceCreated(); onNativeSurfaceChanged()800 public static native void onNativeSurfaceChanged(); onNativeSurfaceDestroyed()801 public static native void onNativeSurfaceDestroyed(); nativeGetHint(String name)802 public static native String nativeGetHint(String name); nativeSetenv(String name, String value)803 public static native void nativeSetenv(String name, String value); onNativeOrientationChanged(int orientation)804 public static native void onNativeOrientationChanged(int orientation); nativeAddTouch(int touchId, String name)805 public static native void nativeAddTouch(int touchId, String name); nativePermissionResult(int requestCode, boolean result)806 public static native void nativePermissionResult(int requestCode, boolean result); 807 808 /** 809 * This method is called by SDL using JNI. 810 */ setActivityTitle(String title)811 public static boolean setActivityTitle(String title) { 812 // Called from SDLMain() thread and can't directly affect the view 813 return mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title); 814 } 815 816 /** 817 * This method is called by SDL using JNI. 818 */ setWindowStyle(boolean fullscreen)819 public static void setWindowStyle(boolean fullscreen) { 820 // Called from SDLMain() thread and can't directly affect the view 821 mSingleton.sendCommand(COMMAND_CHANGE_WINDOW_STYLE, fullscreen ? 1 : 0); 822 } 823 824 /** 825 * This method is called by SDL using JNI. 826 * This is a static method for JNI convenience, it calls a non-static method 827 * so that is can be overridden 828 */ setOrientation(int w, int h, boolean resizable, String hint)829 public static void setOrientation(int w, int h, boolean resizable, String hint) 830 { 831 if (mSingleton != null) { 832 mSingleton.setOrientationBis(w, h, resizable, hint); 833 } 834 } 835 836 /** 837 * This can be overridden 838 */ setOrientationBis(int w, int h, boolean resizable, String hint)839 public void setOrientationBis(int w, int h, boolean resizable, String hint) 840 { 841 int orientation_landscape = -1; 842 int orientation_portrait = -1; 843 844 /* If set, hint "explicitly controls which UI orientations are allowed". */ 845 if (hint.contains("LandscapeRight") && hint.contains("LandscapeLeft")) { 846 orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE; 847 } else if (hint.contains("LandscapeRight")) { 848 orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; 849 } else if (hint.contains("LandscapeLeft")) { 850 orientation_landscape = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; 851 } 852 853 if (hint.contains("Portrait") && hint.contains("PortraitUpsideDown")) { 854 orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT; 855 } else if (hint.contains("Portrait")) { 856 orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; 857 } else if (hint.contains("PortraitUpsideDown")) { 858 orientation_portrait = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT; 859 } 860 861 boolean is_landscape_allowed = (orientation_landscape == -1 ? false : true); 862 boolean is_portrait_allowed = (orientation_portrait == -1 ? false : true); 863 int req = -1; /* Requested orientation */ 864 865 /* No valid hint, nothing is explicitly allowed */ 866 if (!is_portrait_allowed && !is_landscape_allowed) { 867 if (resizable) { 868 /* All orientations are allowed */ 869 req = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR; 870 } else { 871 /* Fixed window and nothing specified. Get orientation from w/h of created window */ 872 req = (w > h ? ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE : ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT); 873 } 874 } else { 875 /* At least one orientation is allowed */ 876 if (resizable) { 877 if (is_portrait_allowed && is_landscape_allowed) { 878 /* hint allows both landscape and portrait, promote to full sensor */ 879 req = ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR; 880 } else { 881 /* Use the only one allowed "orientation" */ 882 req = (is_landscape_allowed ? orientation_landscape : orientation_portrait); 883 } 884 } else { 885 /* Fixed window and both orientations are allowed. Choose one. */ 886 if (is_portrait_allowed && is_landscape_allowed) { 887 req = (w > h ? orientation_landscape : orientation_portrait); 888 } else { 889 /* Use the only one allowed "orientation" */ 890 req = (is_landscape_allowed ? orientation_landscape : orientation_portrait); 891 } 892 } 893 } 894 895 Log.v("SDL", "setOrientation() requestedOrientation=" + req + " width=" + w +" height="+ h +" resizable=" + resizable + " hint=" + hint); 896 if (mAllowSDLOrientationChanges && req != -1) { 897 mSingleton.setRequestedOrientation(req); 898 } 899 } 900 901 /** 902 * This method is called by SDL using JNI. 903 */ minimizeWindow()904 public static void minimizeWindow() { 905 906 if (mSingleton == null) { 907 return; 908 } 909 910 Intent startMain = new Intent(Intent.ACTION_MAIN); 911 startMain.addCategory(Intent.CATEGORY_HOME); 912 startMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 913 mSingleton.startActivity(startMain); 914 } 915 916 /** 917 * This method is called by SDL using JNI. 918 */ shouldMinimizeOnFocusLoss()919 public static boolean shouldMinimizeOnFocusLoss() { 920 /* 921 if (Build.VERSION.SDK_INT >= 24) { 922 if (mSingleton == null) { 923 return true; 924 } 925 926 if (mSingleton.isInMultiWindowMode()) { 927 return false; 928 } 929 930 if (mSingleton.isInPictureInPictureMode()) { 931 return false; 932 } 933 } 934 935 return true; 936 */ 937 return false; 938 } 939 940 /** 941 * This method is called by SDL using JNI. 942 */ isScreenKeyboardShown()943 public static boolean isScreenKeyboardShown() 944 { 945 if (mTextEdit == null) { 946 return false; 947 } 948 949 if (!mScreenKeyboardShown) { 950 return false; 951 } 952 953 InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 954 return imm.isAcceptingText(); 955 956 } 957 958 /** 959 * This method is called by SDL using JNI. 960 */ supportsRelativeMouse()961 public static boolean supportsRelativeMouse() 962 { 963 // ChromeOS doesn't provide relative mouse motion via the Android 7 APIs 964 if (isChromebook()) { 965 return false; 966 } 967 968 // DeX mode in Samsung Experience 9.0 and earlier doesn't support relative mice properly under 969 // Android 7 APIs, and simply returns no data under Android 8 APIs. 970 // 971 // This is fixed in Samsung Experience 9.5, which corresponds to Android 8.1.0, and 972 // thus SDK version 27. If we are in DeX mode and not API 27 or higher, as a result, 973 // we should stick to relative mode. 974 // 975 if ((Build.VERSION.SDK_INT < 27) && isDeXMode()) { 976 return false; 977 } 978 979 return SDLActivity.getMotionListener().supportsRelativeMouse(); 980 } 981 982 /** 983 * This method is called by SDL using JNI. 984 */ setRelativeMouseEnabled(boolean enabled)985 public static boolean setRelativeMouseEnabled(boolean enabled) 986 { 987 if (enabled && !supportsRelativeMouse()) { 988 return false; 989 } 990 991 return SDLActivity.getMotionListener().setRelativeMouseEnabled(enabled); 992 } 993 994 /** 995 * This method is called by SDL using JNI. 996 */ sendMessage(int command, int param)997 public static boolean sendMessage(int command, int param) { 998 if (mSingleton == null) { 999 return false; 1000 } 1001 return mSingleton.sendCommand(command, Integer.valueOf(param)); 1002 } 1003 1004 /** 1005 * This method is called by SDL using JNI. 1006 */ getContext()1007 public static Context getContext() { 1008 return SDL.getContext(); 1009 } 1010 1011 /** 1012 * This method is called by SDL using JNI. 1013 */ isAndroidTV()1014 public static boolean isAndroidTV() { 1015 UiModeManager uiModeManager = (UiModeManager) getContext().getSystemService(UI_MODE_SERVICE); 1016 if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) { 1017 return true; 1018 } 1019 if (Build.MANUFACTURER.equals("MINIX") && Build.MODEL.equals("NEO-U1")) { 1020 return true; 1021 } 1022 if (Build.MANUFACTURER.equals("Amlogic") && Build.MODEL.equals("X96-W")) { 1023 return true; 1024 } 1025 if (Build.MANUFACTURER.equals("Amlogic") && Build.MODEL.startsWith("TV")) { 1026 return true; 1027 } 1028 return false; 1029 } 1030 1031 /** 1032 * This method is called by SDL using JNI. 1033 */ isTablet()1034 public static boolean isTablet() { 1035 DisplayMetrics metrics = new DisplayMetrics(); 1036 Activity activity = (Activity)getContext(); 1037 if (activity == null) { 1038 return false; 1039 } 1040 activity.getWindowManager().getDefaultDisplay().getMetrics(metrics); 1041 1042 double dWidthInches = metrics.widthPixels / (double)metrics.xdpi; 1043 double dHeightInches = metrics.heightPixels / (double)metrics.ydpi; 1044 1045 double dDiagonal = Math.sqrt((dWidthInches * dWidthInches) + (dHeightInches * dHeightInches)); 1046 1047 // If our diagonal size is seven inches or greater, we consider ourselves a tablet. 1048 return (dDiagonal >= 7.0); 1049 } 1050 1051 /** 1052 * This method is called by SDL using JNI. 1053 */ isChromebook()1054 public static boolean isChromebook() { 1055 if (getContext() == null) { 1056 return false; 1057 } 1058 return getContext().getPackageManager().hasSystemFeature("org.chromium.arc.device_management"); 1059 } 1060 1061 /** 1062 * This method is called by SDL using JNI. 1063 */ isDeXMode()1064 public static boolean isDeXMode() { 1065 if (Build.VERSION.SDK_INT < 24) { 1066 return false; 1067 } 1068 try { 1069 final Configuration config = getContext().getResources().getConfiguration(); 1070 final Class configClass = config.getClass(); 1071 return configClass.getField("SEM_DESKTOP_MODE_ENABLED").getInt(configClass) 1072 == configClass.getField("semDesktopModeEnabled").getInt(config); 1073 } catch(Exception ignored) { 1074 return false; 1075 } 1076 } 1077 1078 /** 1079 * This method is called by SDL using JNI. 1080 */ getDisplayDPI()1081 public static DisplayMetrics getDisplayDPI() { 1082 return getContext().getResources().getDisplayMetrics(); 1083 } 1084 1085 /** 1086 * This method is called by SDL using JNI. 1087 */ getManifestEnvironmentVariables()1088 public static boolean getManifestEnvironmentVariables() { 1089 try { 1090 ApplicationInfo applicationInfo = getContext().getPackageManager().getApplicationInfo(getContext().getPackageName(), PackageManager.GET_META_DATA); 1091 Bundle bundle = applicationInfo.metaData; 1092 if (bundle == null) { 1093 return false; 1094 } 1095 String prefix = "SDL_ENV."; 1096 final int trimLength = prefix.length(); 1097 for (String key : bundle.keySet()) { 1098 if (key.startsWith(prefix)) { 1099 String name = key.substring(trimLength); 1100 String value = bundle.get(key).toString(); 1101 nativeSetenv(name, value); 1102 } 1103 } 1104 /* environment variables set! */ 1105 return true; 1106 } catch (Exception e) { 1107 Log.v("SDL", "exception " + e.toString()); 1108 } 1109 return false; 1110 } 1111 1112 // This method is called by SDLControllerManager's API 26 Generic Motion Handler. getContentView()1113 public static View getContentView() 1114 { 1115 return mSingleton.mLayout; 1116 } 1117 1118 static class ShowTextInputTask implements Runnable { 1119 /* 1120 * This is used to regulate the pan&scan method to have some offset from 1121 * the bottom edge of the input region and the top edge of an input 1122 * method (soft keyboard) 1123 */ 1124 static final int HEIGHT_PADDING = 15; 1125 1126 public int x, y, w, h; 1127 ShowTextInputTask(int x, int y, int w, int h)1128 public ShowTextInputTask(int x, int y, int w, int h) { 1129 this.x = x; 1130 this.y = y; 1131 this.w = w; 1132 this.h = h; 1133 1134 /* Minimum size of 1 pixel, so it takes focus. */ 1135 if (this.w <= 0) { 1136 this.w = 1; 1137 } 1138 if (this.h + HEIGHT_PADDING <= 0) { 1139 this.h = 1 - HEIGHT_PADDING; 1140 } 1141 } 1142 1143 @Override run()1144 public void run() { 1145 RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(w, h + HEIGHT_PADDING); 1146 params.leftMargin = x; 1147 params.topMargin = y; 1148 1149 if (mTextEdit == null) { 1150 mTextEdit = new DummyEdit(SDL.getContext()); 1151 1152 mLayout.addView(mTextEdit, params); 1153 } else { 1154 mTextEdit.setLayoutParams(params); 1155 } 1156 1157 mTextEdit.setVisibility(View.VISIBLE); 1158 mTextEdit.requestFocus(); 1159 1160 InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 1161 imm.showSoftInput(mTextEdit, 0); 1162 1163 mScreenKeyboardShown = true; 1164 } 1165 } 1166 1167 /** 1168 * This method is called by SDL using JNI. 1169 */ showTextInput(int x, int y, int w, int h)1170 public static boolean showTextInput(int x, int y, int w, int h) { 1171 // Transfer the task to the main thread as a Runnable 1172 return mSingleton.commandHandler.post(new ShowTextInputTask(x, y, w, h)); 1173 } 1174 isTextInputEvent(KeyEvent event)1175 public static boolean isTextInputEvent(KeyEvent event) { 1176 1177 // Key pressed with Ctrl should be sent as SDL_KEYDOWN/SDL_KEYUP and not SDL_TEXTINPUT 1178 if (event.isCtrlPressed()) { 1179 return false; 1180 } 1181 1182 return event.isPrintingKey() || event.getKeyCode() == KeyEvent.KEYCODE_SPACE; 1183 } 1184 1185 /** 1186 * This method is called by SDL using JNI. 1187 */ getNativeSurface()1188 public static Surface getNativeSurface() { 1189 if (SDLActivity.mSurface == null) { 1190 return null; 1191 } 1192 return SDLActivity.mSurface.getNativeSurface(); 1193 } 1194 1195 /** 1196 * This method is called by SDL using JNI. 1197 */ setSurfaceViewFormat(int format)1198 public static void setSurfaceViewFormat(int format) { 1199 mSingleton.sendCommand(COMMAND_CHANGE_SURFACEVIEW_FORMAT, format); 1200 return; 1201 } 1202 1203 // Input 1204 1205 /** 1206 * This method is called by SDL using JNI. 1207 */ initTouch()1208 public static void initTouch() { 1209 int[] ids = InputDevice.getDeviceIds(); 1210 1211 for (int i = 0; i < ids.length; ++i) { 1212 InputDevice device = InputDevice.getDevice(ids[i]); 1213 if (device != null && (device.getSources() & InputDevice.SOURCE_TOUCHSCREEN) != 0) { 1214 nativeAddTouch(device.getId(), device.getName()); 1215 } 1216 } 1217 } 1218 1219 // APK expansion files support 1220 1221 /** com.android.vending.expansion.zipfile.ZipResourceFile object or null. */ 1222 private static Object expansionFile; 1223 1224 /** com.android.vending.expansion.zipfile.ZipResourceFile's getInputStream() or null. */ 1225 private static Method expansionFileMethod; 1226 1227 /** 1228 * This method is called by SDL using JNI. 1229 * @return an InputStream on success or null if no expansion file was used. 1230 * @throws IOException on errors. Message is set for the SDL error message. 1231 */ openAPKExpansionInputStream(String fileName)1232 public static InputStream openAPKExpansionInputStream(String fileName) throws IOException { 1233 // Get a ZipResourceFile representing a merger of both the main and patch files 1234 if (expansionFile == null) { 1235 String mainHint = nativeGetHint("SDL_ANDROID_APK_EXPANSION_MAIN_FILE_VERSION"); 1236 if (mainHint == null) { 1237 return null; // no expansion use if no main version was set 1238 } 1239 String patchHint = nativeGetHint("SDL_ANDROID_APK_EXPANSION_PATCH_FILE_VERSION"); 1240 if (patchHint == null) { 1241 return null; // no expansion use if no patch version was set 1242 } 1243 1244 Integer mainVersion; 1245 Integer patchVersion; 1246 try { 1247 mainVersion = Integer.valueOf(mainHint); 1248 patchVersion = Integer.valueOf(patchHint); 1249 } catch (NumberFormatException ex) { 1250 ex.printStackTrace(); 1251 throw new IOException("No valid file versions set for APK expansion files", ex); 1252 } 1253 1254 try { 1255 // To avoid direct dependency on Google APK expansion library that is 1256 // not a part of Android SDK we access it using reflection 1257 expansionFile = Class.forName("com.android.vending.expansion.zipfile.APKExpansionSupport") 1258 .getMethod("getAPKExpansionZipFile", Context.class, int.class, int.class) 1259 .invoke(null, SDL.getContext(), mainVersion, patchVersion); 1260 1261 expansionFileMethod = expansionFile.getClass() 1262 .getMethod("getInputStream", String.class); 1263 } catch (Exception ex) { 1264 ex.printStackTrace(); 1265 expansionFile = null; 1266 expansionFileMethod = null; 1267 throw new IOException("Could not access APK expansion support library", ex); 1268 } 1269 } 1270 1271 // Get an input stream for a known file inside the expansion file ZIPs 1272 InputStream fileStream; 1273 try { 1274 fileStream = (InputStream)expansionFileMethod.invoke(expansionFile, fileName); 1275 } catch (Exception ex) { 1276 // calling "getInputStream" failed 1277 ex.printStackTrace(); 1278 throw new IOException("Could not open stream from APK expansion file", ex); 1279 } 1280 1281 if (fileStream == null) { 1282 // calling "getInputStream" was successful but null was returned 1283 throw new IOException("Could not find path in APK expansion file"); 1284 } 1285 1286 return fileStream; 1287 } 1288 1289 // Messagebox 1290 1291 /** Result of current messagebox. Also used for blocking the calling thread. */ 1292 protected final int[] messageboxSelection = new int[1]; 1293 1294 /** Id of current dialog. */ 1295 protected int dialogs = 0; 1296 1297 /** 1298 * This method is called by SDL using JNI. 1299 * Shows the messagebox from UI thread and block calling thread. 1300 * buttonFlags, buttonIds and buttonTexts must have same length. 1301 * @param buttonFlags array containing flags for every button. 1302 * @param buttonIds array containing id for every button. 1303 * @param buttonTexts array containing text for every button. 1304 * @param colors null for default or array of length 5 containing colors. 1305 * @return button id or -1. 1306 */ messageboxShowMessageBox( final int flags, final String title, final String message, final int[] buttonFlags, final int[] buttonIds, final String[] buttonTexts, final int[] colors)1307 public int messageboxShowMessageBox( 1308 final int flags, 1309 final String title, 1310 final String message, 1311 final int[] buttonFlags, 1312 final int[] buttonIds, 1313 final String[] buttonTexts, 1314 final int[] colors) { 1315 1316 messageboxSelection[0] = -1; 1317 1318 // sanity checks 1319 1320 if ((buttonFlags.length != buttonIds.length) && (buttonIds.length != buttonTexts.length)) { 1321 return -1; // implementation broken 1322 } 1323 1324 // collect arguments for Dialog 1325 1326 final Bundle args = new Bundle(); 1327 args.putInt("flags", flags); 1328 args.putString("title", title); 1329 args.putString("message", message); 1330 args.putIntArray("buttonFlags", buttonFlags); 1331 args.putIntArray("buttonIds", buttonIds); 1332 args.putStringArray("buttonTexts", buttonTexts); 1333 args.putIntArray("colors", colors); 1334 1335 // trigger Dialog creation on UI thread 1336 1337 runOnUiThread(new Runnable() { 1338 @Override 1339 public void run() { 1340 showDialog(dialogs++, args); 1341 } 1342 }); 1343 1344 // block the calling thread 1345 1346 synchronized (messageboxSelection) { 1347 try { 1348 messageboxSelection.wait(); 1349 } catch (InterruptedException ex) { 1350 ex.printStackTrace(); 1351 return -1; 1352 } 1353 } 1354 1355 // return selected value 1356 1357 return messageboxSelection[0]; 1358 } 1359 1360 @Override onCreateDialog(int ignore, Bundle args)1361 protected Dialog onCreateDialog(int ignore, Bundle args) { 1362 1363 // TODO set values from "flags" to messagebox dialog 1364 1365 // get colors 1366 1367 int[] colors = args.getIntArray("colors"); 1368 int backgroundColor; 1369 int textColor; 1370 int buttonBorderColor; 1371 int buttonBackgroundColor; 1372 int buttonSelectedColor; 1373 if (colors != null) { 1374 int i = -1; 1375 backgroundColor = colors[++i]; 1376 textColor = colors[++i]; 1377 buttonBorderColor = colors[++i]; 1378 buttonBackgroundColor = colors[++i]; 1379 buttonSelectedColor = colors[++i]; 1380 } else { 1381 backgroundColor = Color.TRANSPARENT; 1382 textColor = Color.TRANSPARENT; 1383 buttonBorderColor = Color.TRANSPARENT; 1384 buttonBackgroundColor = Color.TRANSPARENT; 1385 buttonSelectedColor = Color.TRANSPARENT; 1386 } 1387 1388 // create dialog with title and a listener to wake up calling thread 1389 1390 final Dialog dialog = new Dialog(this); 1391 dialog.setTitle(args.getString("title")); 1392 dialog.setCancelable(false); 1393 dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { 1394 @Override 1395 public void onDismiss(DialogInterface unused) { 1396 synchronized (messageboxSelection) { 1397 messageboxSelection.notify(); 1398 } 1399 } 1400 }); 1401 1402 // create text 1403 1404 TextView message = new TextView(this); 1405 message.setGravity(Gravity.CENTER); 1406 message.setText(args.getString("message")); 1407 if (textColor != Color.TRANSPARENT) { 1408 message.setTextColor(textColor); 1409 } 1410 1411 // create buttons 1412 1413 int[] buttonFlags = args.getIntArray("buttonFlags"); 1414 int[] buttonIds = args.getIntArray("buttonIds"); 1415 String[] buttonTexts = args.getStringArray("buttonTexts"); 1416 1417 final SparseArray<Button> mapping = new SparseArray<Button>(); 1418 1419 LinearLayout buttons = new LinearLayout(this); 1420 buttons.setOrientation(LinearLayout.HORIZONTAL); 1421 buttons.setGravity(Gravity.CENTER); 1422 for (int i = 0; i < buttonTexts.length; ++i) { 1423 Button button = new Button(this); 1424 final int id = buttonIds[i]; 1425 button.setOnClickListener(new View.OnClickListener() { 1426 @Override 1427 public void onClick(View v) { 1428 messageboxSelection[0] = id; 1429 dialog.dismiss(); 1430 } 1431 }); 1432 if (buttonFlags[i] != 0) { 1433 // see SDL_messagebox.h 1434 if ((buttonFlags[i] & 0x00000001) != 0) { 1435 mapping.put(KeyEvent.KEYCODE_ENTER, button); 1436 } 1437 if ((buttonFlags[i] & 0x00000002) != 0) { 1438 mapping.put(KeyEvent.KEYCODE_ESCAPE, button); /* API 11 */ 1439 } 1440 } 1441 button.setText(buttonTexts[i]); 1442 if (textColor != Color.TRANSPARENT) { 1443 button.setTextColor(textColor); 1444 } 1445 if (buttonBorderColor != Color.TRANSPARENT) { 1446 // TODO set color for border of messagebox button 1447 } 1448 if (buttonBackgroundColor != Color.TRANSPARENT) { 1449 Drawable drawable = button.getBackground(); 1450 if (drawable == null) { 1451 // setting the color this way removes the style 1452 button.setBackgroundColor(buttonBackgroundColor); 1453 } else { 1454 // setting the color this way keeps the style (gradient, padding, etc.) 1455 drawable.setColorFilter(buttonBackgroundColor, PorterDuff.Mode.MULTIPLY); 1456 } 1457 } 1458 if (buttonSelectedColor != Color.TRANSPARENT) { 1459 // TODO set color for selected messagebox button 1460 } 1461 buttons.addView(button); 1462 } 1463 1464 // create content 1465 1466 LinearLayout content = new LinearLayout(this); 1467 content.setOrientation(LinearLayout.VERTICAL); 1468 content.addView(message); 1469 content.addView(buttons); 1470 if (backgroundColor != Color.TRANSPARENT) { 1471 content.setBackgroundColor(backgroundColor); 1472 } 1473 1474 // add content to dialog and return 1475 1476 dialog.setContentView(content); 1477 dialog.setOnKeyListener(new Dialog.OnKeyListener() { 1478 @Override 1479 public boolean onKey(DialogInterface d, int keyCode, KeyEvent event) { 1480 Button button = mapping.get(keyCode); 1481 if (button != null) { 1482 if (event.getAction() == KeyEvent.ACTION_UP) { 1483 button.performClick(); 1484 } 1485 return true; // also for ignored actions 1486 } 1487 return false; 1488 } 1489 }); 1490 1491 return dialog; 1492 } 1493 1494 private final Runnable rehideSystemUi = new Runnable() { 1495 @Override 1496 public void run() { 1497 int flags = View.SYSTEM_UI_FLAG_FULLSCREEN | 1498 View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | 1499 View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | 1500 View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | 1501 View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | 1502 View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.INVISIBLE; 1503 1504 SDLActivity.this.getWindow().getDecorView().setSystemUiVisibility(flags); 1505 } 1506 }; 1507 onSystemUiVisibilityChange(int visibility)1508 public void onSystemUiVisibilityChange(int visibility) { 1509 if (SDLActivity.mFullscreenModeActive && ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0 || (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0)) { 1510 1511 Handler handler = getWindow().getDecorView().getHandler(); 1512 if (handler != null) { 1513 handler.removeCallbacks(rehideSystemUi); // Prevent a hide loop. 1514 handler.postDelayed(rehideSystemUi, 2000); 1515 } 1516 1517 } 1518 } 1519 1520 /** 1521 * This method is called by SDL using JNI. 1522 */ clipboardHasText()1523 public static boolean clipboardHasText() { 1524 return mClipboardHandler.clipboardHasText(); 1525 } 1526 1527 /** 1528 * This method is called by SDL using JNI. 1529 */ clipboardGetText()1530 public static String clipboardGetText() { 1531 return mClipboardHandler.clipboardGetText(); 1532 } 1533 1534 /** 1535 * This method is called by SDL using JNI. 1536 */ clipboardSetText(String string)1537 public static void clipboardSetText(String string) { 1538 mClipboardHandler.clipboardSetText(string); 1539 } 1540 1541 /** 1542 * This method is called by SDL using JNI. 1543 */ createCustomCursor(int[] colors, int width, int height, int hotSpotX, int hotSpotY)1544 public static int createCustomCursor(int[] colors, int width, int height, int hotSpotX, int hotSpotY) { 1545 Bitmap bitmap = Bitmap.createBitmap(colors, width, height, Bitmap.Config.ARGB_8888); 1546 ++mLastCursorID; 1547 1548 if (Build.VERSION.SDK_INT >= 24) { 1549 try { 1550 mCursors.put(mLastCursorID, PointerIcon.create(bitmap, hotSpotX, hotSpotY)); 1551 } catch (Exception e) { 1552 return 0; 1553 } 1554 } else { 1555 return 0; 1556 } 1557 return mLastCursorID; 1558 } 1559 1560 /** 1561 * This method is called by SDL using JNI. 1562 */ setCustomCursor(int cursorID)1563 public static boolean setCustomCursor(int cursorID) { 1564 1565 if (Build.VERSION.SDK_INT >= 24) { 1566 try { 1567 mSurface.setPointerIcon(mCursors.get(cursorID)); 1568 } catch (Exception e) { 1569 return false; 1570 } 1571 } else { 1572 return false; 1573 } 1574 return true; 1575 } 1576 1577 /** 1578 * This method is called by SDL using JNI. 1579 */ setSystemCursor(int cursorID)1580 public static boolean setSystemCursor(int cursorID) { 1581 int cursor_type = 0; //PointerIcon.TYPE_NULL; 1582 switch (cursorID) { 1583 case SDL_SYSTEM_CURSOR_ARROW: 1584 cursor_type = 1000; //PointerIcon.TYPE_ARROW; 1585 break; 1586 case SDL_SYSTEM_CURSOR_IBEAM: 1587 cursor_type = 1008; //PointerIcon.TYPE_TEXT; 1588 break; 1589 case SDL_SYSTEM_CURSOR_WAIT: 1590 cursor_type = 1004; //PointerIcon.TYPE_WAIT; 1591 break; 1592 case SDL_SYSTEM_CURSOR_CROSSHAIR: 1593 cursor_type = 1007; //PointerIcon.TYPE_CROSSHAIR; 1594 break; 1595 case SDL_SYSTEM_CURSOR_WAITARROW: 1596 cursor_type = 1004; //PointerIcon.TYPE_WAIT; 1597 break; 1598 case SDL_SYSTEM_CURSOR_SIZENWSE: 1599 cursor_type = 1017; //PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW; 1600 break; 1601 case SDL_SYSTEM_CURSOR_SIZENESW: 1602 cursor_type = 1016; //PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW; 1603 break; 1604 case SDL_SYSTEM_CURSOR_SIZEWE: 1605 cursor_type = 1014; //PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW; 1606 break; 1607 case SDL_SYSTEM_CURSOR_SIZENS: 1608 cursor_type = 1015; //PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW; 1609 break; 1610 case SDL_SYSTEM_CURSOR_SIZEALL: 1611 cursor_type = 1020; //PointerIcon.TYPE_GRAB; 1612 break; 1613 case SDL_SYSTEM_CURSOR_NO: 1614 cursor_type = 1012; //PointerIcon.TYPE_NO_DROP; 1615 break; 1616 case SDL_SYSTEM_CURSOR_HAND: 1617 cursor_type = 1002; //PointerIcon.TYPE_HAND; 1618 break; 1619 } 1620 if (Build.VERSION.SDK_INT >= 24) { 1621 try { 1622 mSurface.setPointerIcon(PointerIcon.getSystemIcon(SDL.getContext(), cursor_type)); 1623 } catch (Exception e) { 1624 return false; 1625 } 1626 } 1627 return true; 1628 } 1629 1630 /** 1631 * This method is called by SDL using JNI. 1632 */ requestPermission(String permission, int requestCode)1633 public static void requestPermission(String permission, int requestCode) { 1634 if (Build.VERSION.SDK_INT < 23) { 1635 nativePermissionResult(requestCode, true); 1636 return; 1637 } 1638 1639 Activity activity = (Activity)getContext(); 1640 if (activity.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) { 1641 activity.requestPermissions(new String[]{permission}, requestCode); 1642 } else { 1643 nativePermissionResult(requestCode, true); 1644 } 1645 } 1646 1647 @Override onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)1648 public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { 1649 if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { 1650 nativePermissionResult(requestCode, true); 1651 } else { 1652 nativePermissionResult(requestCode, false); 1653 } 1654 } 1655 } 1656 1657 /** 1658 Simple runnable to start the SDL application 1659 */ 1660 class SDLMain implements Runnable { 1661 @Override run()1662 public void run() { 1663 // Runs SDL_main() 1664 String library = SDLActivity.mSingleton.getMainSharedObject(); 1665 String function = SDLActivity.mSingleton.getMainFunction(); 1666 String[] arguments = SDLActivity.mSingleton.getArguments(); 1667 1668 try { 1669 android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_DISPLAY); 1670 } catch (Exception e) { 1671 Log.v("SDL", "modify thread properties failed " + e.toString()); 1672 } 1673 1674 Log.v("SDL", "Running main function " + function + " from library " + library); 1675 1676 SDLActivity.nativeRunMain(library, function, arguments); 1677 1678 Log.v("SDL", "Finished main function"); 1679 1680 if (SDLActivity.mSingleton == null || SDLActivity.mSingleton.isFinishing()) { 1681 // Activity is already being destroyed 1682 } else { 1683 // Let's finish the Activity 1684 SDLActivity.mSDLThread = null; 1685 SDLActivity.mSingleton.finish(); 1686 } 1687 } 1688 } 1689 1690 1691 /** 1692 SDLSurface. This is what we draw on, so we need to know when it's created 1693 in order to do anything useful. 1694 1695 Because of this, that's where we set up the SDL thread 1696 */ 1697 class SDLSurface extends SurfaceView implements SurfaceHolder.Callback, 1698 View.OnKeyListener, View.OnTouchListener, SensorEventListener { 1699 1700 // Sensors 1701 protected SensorManager mSensorManager; 1702 protected Display mDisplay; 1703 1704 // Keep track of the surface size to normalize touch events 1705 protected float mWidth, mHeight; 1706 1707 // Is SurfaceView ready for rendering 1708 public boolean mIsSurfaceReady; 1709 1710 // Startup SDLSurface(Context context)1711 public SDLSurface(Context context) { 1712 super(context); 1713 getHolder().addCallback(this); 1714 1715 setFocusable(true); 1716 setFocusableInTouchMode(true); 1717 requestFocus(); 1718 setOnKeyListener(this); 1719 setOnTouchListener(this); 1720 1721 mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); 1722 mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE); 1723 1724 setOnGenericMotionListener(SDLActivity.getMotionListener()); 1725 1726 // Some arbitrary defaults to avoid a potential division by zero 1727 mWidth = 1.0f; 1728 mHeight = 1.0f; 1729 1730 mIsSurfaceReady = false; 1731 } 1732 handlePause()1733 public void handlePause() { 1734 enableSensor(Sensor.TYPE_ACCELEROMETER, false); 1735 } 1736 handleResume()1737 public void handleResume() { 1738 setFocusable(true); 1739 setFocusableInTouchMode(true); 1740 requestFocus(); 1741 setOnKeyListener(this); 1742 setOnTouchListener(this); 1743 enableSensor(Sensor.TYPE_ACCELEROMETER, true); 1744 } 1745 getNativeSurface()1746 public Surface getNativeSurface() { 1747 return getHolder().getSurface(); 1748 } 1749 1750 // Called when we have a valid drawing surface 1751 @Override surfaceCreated(SurfaceHolder holder)1752 public void surfaceCreated(SurfaceHolder holder) { 1753 Log.v("SDL", "surfaceCreated()"); 1754 SDLActivity.onNativeSurfaceCreated(); 1755 } 1756 1757 // Called when we lose the surface 1758 @Override surfaceDestroyed(SurfaceHolder holder)1759 public void surfaceDestroyed(SurfaceHolder holder) { 1760 Log.v("SDL", "surfaceDestroyed()"); 1761 1762 // Transition to pause, if needed 1763 SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED; 1764 SDLActivity.handleNativeState(); 1765 1766 mIsSurfaceReady = false; 1767 SDLActivity.onNativeSurfaceDestroyed(); 1768 } 1769 1770 // Called when the surface is resized 1771 @Override surfaceChanged(SurfaceHolder holder, int format, int width, int height)1772 public void surfaceChanged(SurfaceHolder holder, 1773 int format, int width, int height) { 1774 Log.v("SDL", "surfaceChanged()"); 1775 1776 if (SDLActivity.mSingleton == null) { 1777 return; 1778 } 1779 1780 int sdlFormat = 0x15151002; // SDL_PIXELFORMAT_RGB565 by default 1781 switch (format) { 1782 case PixelFormat.RGBA_8888: 1783 Log.v("SDL", "pixel format RGBA_8888"); 1784 sdlFormat = 0x16462004; // SDL_PIXELFORMAT_RGBA8888 1785 break; 1786 case PixelFormat.RGBX_8888: 1787 Log.v("SDL", "pixel format RGBX_8888"); 1788 sdlFormat = 0x16261804; // SDL_PIXELFORMAT_RGBX8888 1789 break; 1790 case PixelFormat.RGB_565: 1791 Log.v("SDL", "pixel format RGB_565"); 1792 sdlFormat = 0x15151002; // SDL_PIXELFORMAT_RGB565 1793 break; 1794 case PixelFormat.RGB_888: 1795 Log.v("SDL", "pixel format RGB_888"); 1796 // Not sure this is right, maybe SDL_PIXELFORMAT_RGB24 instead? 1797 sdlFormat = 0x16161804; // SDL_PIXELFORMAT_RGB888 1798 break; 1799 default: 1800 Log.v("SDL", "pixel format unknown " + format); 1801 break; 1802 } 1803 1804 mWidth = width; 1805 mHeight = height; 1806 int nDeviceWidth = width; 1807 int nDeviceHeight = height; 1808 try 1809 { 1810 if (Build.VERSION.SDK_INT >= 17) { 1811 android.util.DisplayMetrics realMetrics = new android.util.DisplayMetrics(); 1812 mDisplay.getRealMetrics( realMetrics ); 1813 nDeviceWidth = realMetrics.widthPixels; 1814 nDeviceHeight = realMetrics.heightPixels; 1815 } 1816 } 1817 catch ( java.lang.Throwable throwable ) {} 1818 1819 synchronized(SDLActivity.getContext()) { 1820 // In case we're waiting on a size change after going fullscreen, send a notification. 1821 SDLActivity.getContext().notifyAll(); 1822 } 1823 1824 Log.v("SDL", "Window size: " + width + "x" + height); 1825 Log.v("SDL", "Device size: " + nDeviceWidth + "x" + nDeviceHeight); 1826 SDLActivity.nativeSetScreenResolution(width, height, nDeviceWidth, nDeviceHeight, sdlFormat, mDisplay.getRefreshRate()); 1827 SDLActivity.onNativeResize(); 1828 1829 // Prevent a screen distortion glitch, 1830 // for instance when the device is in Landscape and a Portrait App is resumed. 1831 boolean skip = false; 1832 int requestedOrientation = SDLActivity.mAllowSDLOrientationChanges ? SDLActivity.mSingleton.getRequestedOrientation() : ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE; 1833 1834 if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) 1835 { 1836 // Accept any 1837 } 1838 else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) 1839 { 1840 if (mWidth > mHeight) { 1841 skip = true; 1842 } 1843 } else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) { 1844 if (mWidth < mHeight) { 1845 skip = true; 1846 } 1847 } 1848 1849 // Special Patch for Square Resolution: Black Berry Passport 1850 if (skip) { 1851 double min = Math.min(mWidth, mHeight); 1852 double max = Math.max(mWidth, mHeight); 1853 1854 if (max / min < 1.20) { 1855 Log.v("SDL", "Don't skip on such aspect-ratio. Could be a square resolution."); 1856 skip = false; 1857 } 1858 } 1859 1860 // Don't skip in MultiWindow. 1861 if (skip) { 1862 if (Build.VERSION.SDK_INT >= 24) { 1863 if (SDLActivity.mSingleton.isInMultiWindowMode()) { 1864 Log.v("SDL", "Don't skip in Multi-Window"); 1865 skip = false; 1866 } 1867 } 1868 } 1869 1870 if (skip) { 1871 Log.v("SDL", "Skip .. Surface is not ready."); 1872 mIsSurfaceReady = false; 1873 return; 1874 } 1875 1876 /* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */ 1877 SDLActivity.onNativeSurfaceChanged(); 1878 1879 /* Surface is ready */ 1880 mIsSurfaceReady = true; 1881 1882 SDLActivity.mNextNativeState = SDLActivity.NativeState.RESUMED; 1883 SDLActivity.handleNativeState(); 1884 } 1885 1886 // Key events 1887 @Override onKey(View v, int keyCode, KeyEvent event)1888 public boolean onKey(View v, int keyCode, KeyEvent event) { 1889 1890 int deviceId = event.getDeviceId(); 1891 int source = event.getSource(); 1892 1893 // Dispatch the different events depending on where they come from 1894 // Some SOURCE_JOYSTICK, SOURCE_DPAD or SOURCE_GAMEPAD are also SOURCE_KEYBOARD 1895 // So, we try to process them as JOYSTICK/DPAD/GAMEPAD events first, if that fails we try them as KEYBOARD 1896 // 1897 // Furthermore, it's possible a game controller has SOURCE_KEYBOARD and 1898 // SOURCE_JOYSTICK, while its key events arrive from the keyboard source 1899 // So, retrieve the device itself and check all of its sources 1900 if (SDLControllerManager.isDeviceSDLJoystick(deviceId)) { 1901 // Note that we process events with specific key codes here 1902 if (event.getAction() == KeyEvent.ACTION_DOWN) { 1903 if (SDLControllerManager.onNativePadDown(deviceId, keyCode) == 0) { 1904 return true; 1905 } 1906 } else if (event.getAction() == KeyEvent.ACTION_UP) { 1907 if (SDLControllerManager.onNativePadUp(deviceId, keyCode) == 0) { 1908 return true; 1909 } 1910 } 1911 } 1912 1913 if (source == InputDevice.SOURCE_UNKNOWN) { 1914 InputDevice device = InputDevice.getDevice(deviceId); 1915 if (device != null) { 1916 source = device.getSources(); 1917 } 1918 } 1919 1920 if ((source & InputDevice.SOURCE_KEYBOARD) != 0) { 1921 if (event.getAction() == KeyEvent.ACTION_DOWN) { 1922 //Log.v("SDL", "key down: " + keyCode); 1923 if (SDLActivity.isTextInputEvent(event)) { 1924 SDLInputConnection.nativeCommitText(String.valueOf((char) event.getUnicodeChar()), 1); 1925 } 1926 SDLActivity.onNativeKeyDown(keyCode); 1927 return true; 1928 } 1929 else if (event.getAction() == KeyEvent.ACTION_UP) { 1930 //Log.v("SDL", "key up: " + keyCode); 1931 SDLActivity.onNativeKeyUp(keyCode); 1932 return true; 1933 } 1934 } 1935 1936 if ((source & InputDevice.SOURCE_MOUSE) != 0) { 1937 // on some devices key events are sent for mouse BUTTON_BACK/FORWARD presses 1938 // they are ignored here because sending them as mouse input to SDL is messy 1939 if ((keyCode == KeyEvent.KEYCODE_BACK) || (keyCode == KeyEvent.KEYCODE_FORWARD)) { 1940 switch (event.getAction()) { 1941 case KeyEvent.ACTION_DOWN: 1942 case KeyEvent.ACTION_UP: 1943 // mark the event as handled or it will be handled by system 1944 // handling KEYCODE_BACK by system will call onBackPressed() 1945 return true; 1946 } 1947 } 1948 } 1949 1950 return false; 1951 } 1952 1953 // Touch events 1954 @Override onTouch(View v, MotionEvent event)1955 public boolean onTouch(View v, MotionEvent event) { 1956 /* Ref: http://developer.android.com/training/gestures/multi.html */ 1957 final int touchDevId = event.getDeviceId(); 1958 final int pointerCount = event.getPointerCount(); 1959 int action = event.getActionMasked(); 1960 int pointerFingerId; 1961 int mouseButton; 1962 int i = -1; 1963 float x,y,p; 1964 1965 // 12290 = Samsung DeX mode desktop mouse 1966 // 12290 = 0x3002 = 0x2002 | 0x1002 = SOURCE_MOUSE | SOURCE_TOUCHSCREEN 1967 // 0x2 = SOURCE_CLASS_POINTER 1968 if (event.getSource() == InputDevice.SOURCE_MOUSE || event.getSource() == (InputDevice.SOURCE_MOUSE | InputDevice.SOURCE_TOUCHSCREEN)) { 1969 try { 1970 mouseButton = (Integer) event.getClass().getMethod("getButtonState").invoke(event); 1971 } catch(Exception e) { 1972 mouseButton = 1; // oh well. 1973 } 1974 1975 // We need to check if we're in relative mouse mode and get the axis offset rather than the x/y values 1976 // if we are. We'll leverage our existing mouse motion listener 1977 SDLGenericMotionListener_API12 motionListener = SDLActivity.getMotionListener(); 1978 x = motionListener.getEventX(event); 1979 y = motionListener.getEventY(event); 1980 1981 SDLActivity.onNativeMouse(mouseButton, action, x, y, motionListener.inRelativeMode()); 1982 } else { 1983 switch(action) { 1984 case MotionEvent.ACTION_MOVE: 1985 for (i = 0; i < pointerCount; i++) { 1986 pointerFingerId = event.getPointerId(i); 1987 x = event.getX(i) / mWidth; 1988 y = event.getY(i) / mHeight; 1989 p = event.getPressure(i); 1990 if (p > 1.0f) { 1991 // may be larger than 1.0f on some devices 1992 // see the documentation of getPressure(i) 1993 p = 1.0f; 1994 } 1995 SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p); 1996 } 1997 break; 1998 1999 case MotionEvent.ACTION_UP: 2000 case MotionEvent.ACTION_DOWN: 2001 // Primary pointer up/down, the index is always zero 2002 i = 0; 2003 case MotionEvent.ACTION_POINTER_UP: 2004 case MotionEvent.ACTION_POINTER_DOWN: 2005 // Non primary pointer up/down 2006 if (i == -1) { 2007 i = event.getActionIndex(); 2008 } 2009 2010 pointerFingerId = event.getPointerId(i); 2011 x = event.getX(i) / mWidth; 2012 y = event.getY(i) / mHeight; 2013 p = event.getPressure(i); 2014 if (p > 1.0f) { 2015 // may be larger than 1.0f on some devices 2016 // see the documentation of getPressure(i) 2017 p = 1.0f; 2018 } 2019 SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p); 2020 break; 2021 2022 case MotionEvent.ACTION_CANCEL: 2023 for (i = 0; i < pointerCount; i++) { 2024 pointerFingerId = event.getPointerId(i); 2025 x = event.getX(i) / mWidth; 2026 y = event.getY(i) / mHeight; 2027 p = event.getPressure(i); 2028 if (p > 1.0f) { 2029 // may be larger than 1.0f on some devices 2030 // see the documentation of getPressure(i) 2031 p = 1.0f; 2032 } 2033 SDLActivity.onNativeTouch(touchDevId, pointerFingerId, MotionEvent.ACTION_UP, x, y, p); 2034 } 2035 break; 2036 2037 default: 2038 break; 2039 } 2040 } 2041 2042 return true; 2043 } 2044 2045 // Sensor events enableSensor(int sensortype, boolean enabled)2046 public void enableSensor(int sensortype, boolean enabled) { 2047 // TODO: This uses getDefaultSensor - what if we have >1 accels? 2048 if (enabled) { 2049 mSensorManager.registerListener(this, 2050 mSensorManager.getDefaultSensor(sensortype), 2051 SensorManager.SENSOR_DELAY_GAME, null); 2052 } else { 2053 mSensorManager.unregisterListener(this, 2054 mSensorManager.getDefaultSensor(sensortype)); 2055 } 2056 } 2057 2058 @Override onAccuracyChanged(Sensor sensor, int accuracy)2059 public void onAccuracyChanged(Sensor sensor, int accuracy) { 2060 // TODO 2061 } 2062 2063 @Override onSensorChanged(SensorEvent event)2064 public void onSensorChanged(SensorEvent event) { 2065 if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { 2066 2067 // Since we may have an orientation set, we won't receive onConfigurationChanged events. 2068 // We thus should check here. 2069 int newOrientation = SDLActivity.SDL_ORIENTATION_UNKNOWN; 2070 2071 float x, y; 2072 switch (mDisplay.getRotation()) { 2073 case Surface.ROTATION_90: 2074 x = -event.values[1]; 2075 y = event.values[0]; 2076 newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE; 2077 break; 2078 case Surface.ROTATION_270: 2079 x = event.values[1]; 2080 y = -event.values[0]; 2081 newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE_FLIPPED; 2082 break; 2083 case Surface.ROTATION_180: 2084 x = -event.values[0]; 2085 y = -event.values[1]; 2086 newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT_FLIPPED; 2087 break; 2088 default: 2089 x = event.values[0]; 2090 y = event.values[1]; 2091 newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT; 2092 break; 2093 } 2094 2095 if (newOrientation != SDLActivity.mCurrentOrientation) { 2096 SDLActivity.mCurrentOrientation = newOrientation; 2097 SDLActivity.onNativeOrientationChanged(newOrientation); 2098 } 2099 2100 SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH, 2101 y / SensorManager.GRAVITY_EARTH, 2102 event.values[2] / SensorManager.GRAVITY_EARTH); 2103 2104 2105 } 2106 } 2107 2108 // Captured pointer events for API 26. onCapturedPointerEvent(MotionEvent event)2109 public boolean onCapturedPointerEvent(MotionEvent event) 2110 { 2111 int action = event.getActionMasked(); 2112 2113 float x, y; 2114 switch (action) { 2115 case MotionEvent.ACTION_SCROLL: 2116 x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0); 2117 y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0); 2118 SDLActivity.onNativeMouse(0, action, x, y, false); 2119 return true; 2120 2121 case MotionEvent.ACTION_HOVER_MOVE: 2122 case MotionEvent.ACTION_MOVE: 2123 x = event.getX(0); 2124 y = event.getY(0); 2125 SDLActivity.onNativeMouse(0, action, x, y, true); 2126 return true; 2127 2128 case MotionEvent.ACTION_BUTTON_PRESS: 2129 case MotionEvent.ACTION_BUTTON_RELEASE: 2130 2131 // Change our action value to what SDL's code expects. 2132 if (action == MotionEvent.ACTION_BUTTON_PRESS) { 2133 action = MotionEvent.ACTION_DOWN; 2134 } 2135 else if (action == MotionEvent.ACTION_BUTTON_RELEASE) { 2136 action = MotionEvent.ACTION_UP; 2137 } 2138 2139 x = event.getX(0); 2140 y = event.getY(0); 2141 int button = event.getButtonState(); 2142 2143 SDLActivity.onNativeMouse(button, action, x, y, true); 2144 return true; 2145 } 2146 2147 return false; 2148 } 2149 2150 } 2151 2152 /* This is a fake invisible editor view that receives the input and defines the 2153 * pan&scan region 2154 */ 2155 class DummyEdit extends View implements View.OnKeyListener { 2156 InputConnection ic; 2157 DummyEdit(Context context)2158 public DummyEdit(Context context) { 2159 super(context); 2160 setFocusableInTouchMode(true); 2161 setFocusable(true); 2162 setOnKeyListener(this); 2163 } 2164 2165 @Override onCheckIsTextEditor()2166 public boolean onCheckIsTextEditor() { 2167 return true; 2168 } 2169 2170 @Override onKey(View v, int keyCode, KeyEvent event)2171 public boolean onKey(View v, int keyCode, KeyEvent event) { 2172 /* 2173 * This handles the hardware keyboard input 2174 */ 2175 if (event.getAction() == KeyEvent.ACTION_DOWN) { 2176 if (SDLActivity.isTextInputEvent(event)) { 2177 ic.commitText(String.valueOf((char) event.getUnicodeChar()), 1); 2178 return true; 2179 } 2180 SDLActivity.onNativeKeyDown(keyCode); 2181 return true; 2182 } else if (event.getAction() == KeyEvent.ACTION_UP) { 2183 SDLActivity.onNativeKeyUp(keyCode); 2184 return true; 2185 } 2186 return false; 2187 } 2188 2189 // 2190 @Override onKeyPreIme(int keyCode, KeyEvent event)2191 public boolean onKeyPreIme (int keyCode, KeyEvent event) { 2192 // As seen on StackOverflow: http://stackoverflow.com/questions/7634346/keyboard-hide-event 2193 // FIXME: Discussion at http://bugzilla.libsdl.org/show_bug.cgi?id=1639 2194 // FIXME: This is not a 100% effective solution to the problem of detecting if the keyboard is showing or not 2195 // FIXME: A more effective solution would be to assume our Layout to be RelativeLayout or LinearLayout 2196 // FIXME: And determine the keyboard presence doing this: http://stackoverflow.com/questions/2150078/how-to-check-visibility-of-software-keyboard-in-android 2197 // FIXME: An even more effective way would be if Android provided this out of the box, but where would the fun be in that :) 2198 if (event.getAction()==KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) { 2199 if (SDLActivity.mTextEdit != null && SDLActivity.mTextEdit.getVisibility() == View.VISIBLE) { 2200 SDLActivity.onNativeKeyboardFocusLost(); 2201 } 2202 } 2203 return super.onKeyPreIme(keyCode, event); 2204 } 2205 2206 @Override onCreateInputConnection(EditorInfo outAttrs)2207 public InputConnection onCreateInputConnection(EditorInfo outAttrs) { 2208 ic = new SDLInputConnection(this, true); 2209 2210 outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD; 2211 outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI 2212 | EditorInfo.IME_FLAG_NO_FULLSCREEN /* API 11 */; 2213 2214 return ic; 2215 } 2216 } 2217 2218 class SDLInputConnection extends BaseInputConnection { 2219 SDLInputConnection(View targetView, boolean fullEditor)2220 public SDLInputConnection(View targetView, boolean fullEditor) { 2221 super(targetView, fullEditor); 2222 2223 } 2224 2225 @Override sendKeyEvent(KeyEvent event)2226 public boolean sendKeyEvent(KeyEvent event) { 2227 /* 2228 * This used to handle the keycodes from soft keyboard (and IME-translated input from hardkeyboard) 2229 * However, as of Ice Cream Sandwich and later, almost all soft keyboard doesn't generate key presses 2230 * and so we need to generate them ourselves in commitText. To avoid duplicates on the handful of keys 2231 * that still do, we empty this out. 2232 */ 2233 2234 /* 2235 * Return DOES still generate a key event, however. So rather than using it as the 'click a button' key 2236 * as we do with physical keyboards, let's just use it to hide the keyboard. 2237 */ 2238 2239 if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) { 2240 if (SDLActivity.onNativeSoftReturnKey()) { 2241 return true; 2242 } 2243 } 2244 2245 2246 return super.sendKeyEvent(event); 2247 } 2248 2249 @Override commitText(CharSequence text, int newCursorPosition)2250 public boolean commitText(CharSequence text, int newCursorPosition) { 2251 2252 for (int i = 0; i < text.length(); i++) { 2253 char c = text.charAt(i); 2254 if (c == '\n') { 2255 if (SDLActivity.onNativeSoftReturnKey()) { 2256 return true; 2257 } 2258 } 2259 nativeGenerateScancodeForUnichar(c); 2260 } 2261 2262 SDLInputConnection.nativeCommitText(text.toString(), newCursorPosition); 2263 2264 return super.commitText(text, newCursorPosition); 2265 } 2266 2267 @Override setComposingText(CharSequence text, int newCursorPosition)2268 public boolean setComposingText(CharSequence text, int newCursorPosition) { 2269 2270 nativeSetComposingText(text.toString(), newCursorPosition); 2271 2272 return super.setComposingText(text, newCursorPosition); 2273 } 2274 nativeCommitText(String text, int newCursorPosition)2275 public static native void nativeCommitText(String text, int newCursorPosition); 2276 nativeGenerateScancodeForUnichar(char c)2277 public native void nativeGenerateScancodeForUnichar(char c); 2278 nativeSetComposingText(String text, int newCursorPosition)2279 public native void nativeSetComposingText(String text, int newCursorPosition); 2280 2281 @Override deleteSurroundingText(int beforeLength, int afterLength)2282 public boolean deleteSurroundingText(int beforeLength, int afterLength) { 2283 // Workaround to capture backspace key. Ref: http://stackoverflow.com/questions/14560344/android-backspace-in-webview-baseinputconnection 2284 // and https://bugzilla.libsdl.org/show_bug.cgi?id=2265 2285 if (beforeLength > 0 && afterLength == 0) { 2286 boolean ret = true; 2287 // backspace(s) 2288 while (beforeLength-- > 0) { 2289 boolean ret_key = sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL)) 2290 && sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL)); 2291 ret = ret && ret_key; 2292 } 2293 return ret; 2294 } 2295 2296 return super.deleteSurroundingText(beforeLength, afterLength); 2297 } 2298 } 2299 2300 interface SDLClipboardHandler { 2301 clipboardHasText()2302 public boolean clipboardHasText(); clipboardGetText()2303 public String clipboardGetText(); clipboardSetText(String string)2304 public void clipboardSetText(String string); 2305 2306 } 2307 2308 2309 class SDLClipboardHandler_API11 implements 2310 SDLClipboardHandler, 2311 android.content.ClipboardManager.OnPrimaryClipChangedListener { 2312 2313 protected android.content.ClipboardManager mClipMgr; 2314 SDLClipboardHandler_API11()2315 SDLClipboardHandler_API11() { 2316 mClipMgr = (android.content.ClipboardManager) SDL.getContext().getSystemService(Context.CLIPBOARD_SERVICE); 2317 mClipMgr.addPrimaryClipChangedListener(this); 2318 } 2319 2320 @Override clipboardHasText()2321 public boolean clipboardHasText() { 2322 return mClipMgr.hasText(); 2323 } 2324 2325 @Override clipboardGetText()2326 public String clipboardGetText() { 2327 CharSequence text; 2328 text = mClipMgr.getText(); 2329 if (text != null) { 2330 return text.toString(); 2331 } 2332 return null; 2333 } 2334 2335 @Override clipboardSetText(String string)2336 public void clipboardSetText(String string) { 2337 mClipMgr.removePrimaryClipChangedListener(this); 2338 mClipMgr.setText(string); 2339 mClipMgr.addPrimaryClipChangedListener(this); 2340 } 2341 2342 @Override onPrimaryClipChanged()2343 public void onPrimaryClipChanged() { 2344 SDLActivity.onNativeClipboardChanged(); 2345 } 2346 2347 } 2348 2349