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