2021-04-18 07:35:25 +04:00
package org.libsdl.app ;
2021-04-20 23:40:33 +04:00
import android.app.Activity ;
import android.app.AlertDialog ;
import android.app.Dialog ;
import android.app.UiModeManager ;
import android.content.ClipboardManager ;
import android.content.ClipData ;
import android.content.Context ;
import android.content.DialogInterface ;
import android.content.Intent ;
import android.content.pm.ActivityInfo ;
import android.content.pm.ApplicationInfo ;
import android.content.pm.PackageManager ;
2021-04-18 07:35:25 +04:00
import android.content.res.Configuration ;
2021-04-20 23:40:33 +04:00
import android.graphics.Bitmap ;
import android.graphics.Color ;
import android.graphics.PixelFormat ;
import android.graphics.PorterDuff ;
import android.graphics.drawable.Drawable ;
import android.hardware.Sensor ;
import android.hardware.SensorEvent ;
import android.hardware.SensorEventListener ;
import android.hardware.SensorManager ;
import android.net.Uri ;
import android.os.Build ;
import android.os.Bundle ;
import android.os.Handler ;
import android.os.Message ;
2021-04-18 07:35:25 +04:00
import android.text.InputType ;
2021-04-20 23:40:33 +04:00
import android.util.DisplayMetrics ;
import android.util.Log ;
import android.util.SparseArray ;
import android.view.Display ;
import android.view.Gravity ;
import android.view.InputDevice ;
import android.view.KeyEvent ;
import android.view.MotionEvent ;
import android.view.PointerIcon ;
import android.view.Surface ;
import android.view.SurfaceHolder ;
import android.view.SurfaceView ;
import android.view.View ;
import android.view.ViewGroup ;
import android.view.Window ;
import android.view.WindowManager ;
2021-04-18 07:35:25 +04:00
import android.view.inputmethod.BaseInputConnection ;
import android.view.inputmethod.EditorInfo ;
import android.view.inputmethod.InputConnection ;
import android.view.inputmethod.InputMethodManager ;
import android.widget.Button ;
import android.widget.LinearLayout ;
2021-04-20 23:40:33 +04:00
import android.widget.RelativeLayout ;
2021-04-18 07:35:25 +04:00
import android.widget.TextView ;
2021-05-09 13:30:38 +04:00
import android.widget.Toast ;
2021-04-20 23:40:33 +04:00
import java.util.Hashtable ;
import java.util.Locale ;
2021-04-18 07:35:25 +04:00
/ * *
SDL Activity
* /
public class SDLActivity extends Activity implements View . OnSystemUiVisibilityChangeListener {
private static final String TAG = " SDL " ;
2022-07-15 06:00:50 +04:00
/ *
// Display InputType.SOURCE/CLASS of events and devices
//
// SDLActivity.debugSource(device.getSources(), "device[" + device.getName() + "]");
// SDLActivity.debugSource(event.getSource(), "event");
public static void debugSource ( int sources , String prefix ) {
int s = sources ;
int s_copy = sources ;
String cls = " " ;
String src = " " ;
int tst = 0 ;
int FLAG_TAINTED = 0x80000000 ;
if ( ( s & InputDevice . SOURCE_CLASS_BUTTON ) ! = 0 ) cls + = " BUTTON " ;
if ( ( s & InputDevice . SOURCE_CLASS_JOYSTICK ) ! = 0 ) cls + = " JOYSTICK " ;
if ( ( s & InputDevice . SOURCE_CLASS_POINTER ) ! = 0 ) cls + = " POINTER " ;
if ( ( s & InputDevice . SOURCE_CLASS_POSITION ) ! = 0 ) cls + = " POSITION " ;
if ( ( s & InputDevice . SOURCE_CLASS_TRACKBALL ) ! = 0 ) cls + = " TRACKBALL " ;
int s2 = s_copy & ~ InputDevice . SOURCE_ANY ; // keep class bits
s2 & = ~ ( InputDevice . SOURCE_CLASS_BUTTON
| InputDevice . SOURCE_CLASS_JOYSTICK
| InputDevice . SOURCE_CLASS_POINTER
| InputDevice . SOURCE_CLASS_POSITION
| InputDevice . SOURCE_CLASS_TRACKBALL ) ;
if ( s2 ! = 0 ) cls + = " Some_Unkown " ;
s2 = s_copy & InputDevice . SOURCE_ANY ; // keep source only, no class;
if ( Build . VERSION . SDK_INT > = Build . VERSION_CODES . M ) {
tst = InputDevice . SOURCE_BLUETOOTH_STYLUS ;
if ( ( s & tst ) = = tst ) src + = " BLUETOOTH_STYLUS " ;
s2 & = ~ tst ;
}
tst = InputDevice . SOURCE_DPAD ;
if ( ( s & tst ) = = tst ) src + = " DPAD " ;
s2 & = ~ tst ;
tst = InputDevice . SOURCE_GAMEPAD ;
if ( ( s & tst ) = = tst ) src + = " GAMEPAD " ;
s2 & = ~ tst ;
if ( Build . VERSION . SDK_INT > = Build . VERSION_CODES . LOLLIPOP ) {
tst = InputDevice . SOURCE_HDMI ;
if ( ( s & tst ) = = tst ) src + = " HDMI " ;
s2 & = ~ tst ;
}
tst = InputDevice . SOURCE_JOYSTICK ;
if ( ( s & tst ) = = tst ) src + = " JOYSTICK " ;
s2 & = ~ tst ;
tst = InputDevice . SOURCE_KEYBOARD ;
if ( ( s & tst ) = = tst ) src + = " KEYBOARD " ;
s2 & = ~ tst ;
tst = InputDevice . SOURCE_MOUSE ;
if ( ( s & tst ) = = tst ) src + = " MOUSE " ;
s2 & = ~ tst ;
if ( Build . VERSION . SDK_INT > = 26 ) {
tst = InputDevice . SOURCE_MOUSE_RELATIVE ;
if ( ( s & tst ) = = tst ) src + = " MOUSE_RELATIVE " ;
s2 & = ~ tst ;
tst = InputDevice . SOURCE_ROTARY_ENCODER ;
if ( ( s & tst ) = = tst ) src + = " ROTARY_ENCODER " ;
s2 & = ~ tst ;
}
tst = InputDevice . SOURCE_STYLUS ;
if ( ( s & tst ) = = tst ) src + = " STYLUS " ;
s2 & = ~ tst ;
tst = InputDevice . SOURCE_TOUCHPAD ;
if ( ( s & tst ) = = tst ) src + = " TOUCHPAD " ;
s2 & = ~ tst ;
tst = InputDevice . SOURCE_TOUCHSCREEN ;
if ( ( s & tst ) = = tst ) src + = " TOUCHSCREEN " ;
s2 & = ~ tst ;
if ( Build . VERSION . SDK_INT > = Build . VERSION_CODES . JELLY_BEAN_MR2 ) {
tst = InputDevice . SOURCE_TOUCH_NAVIGATION ;
if ( ( s & tst ) = = tst ) src + = " TOUCH_NAVIGATION " ;
s2 & = ~ tst ;
}
tst = InputDevice . SOURCE_TRACKBALL ;
if ( ( s & tst ) = = tst ) src + = " TRACKBALL " ;
s2 & = ~ tst ;
tst = InputDevice . SOURCE_ANY ;
if ( ( s & tst ) = = tst ) src + = " ANY " ;
s2 & = ~ tst ;
if ( s = = FLAG_TAINTED ) src + = " FLAG_TAINTED " ;
s2 & = ~ FLAG_TAINTED ;
if ( s2 ! = 0 ) src + = " Some_Unkown " ;
Log . v ( TAG , prefix + " int= " + s_copy + " CLASS={ " + cls + " } source(s): " + src ) ;
}
* /
2021-04-18 07:35:25 +04:00
public static boolean mIsResumedCalled , mHasFocus ;
public static final boolean mHasMultiWindow = ( Build . VERSION . SDK_INT > = 24 ) ;
// Cursor types
2021-04-20 23:40:33 +04:00
// private static final int SDL_SYSTEM_CURSOR_NONE = -1;
2021-04-18 07:35:25 +04:00
private static final int SDL_SYSTEM_CURSOR_ARROW = 0 ;
private static final int SDL_SYSTEM_CURSOR_IBEAM = 1 ;
private static final int SDL_SYSTEM_CURSOR_WAIT = 2 ;
private static final int SDL_SYSTEM_CURSOR_CROSSHAIR = 3 ;
private static final int SDL_SYSTEM_CURSOR_WAITARROW = 4 ;
private static final int SDL_SYSTEM_CURSOR_SIZENWSE = 5 ;
private static final int SDL_SYSTEM_CURSOR_SIZENESW = 6 ;
private static final int SDL_SYSTEM_CURSOR_SIZEWE = 7 ;
private static final int SDL_SYSTEM_CURSOR_SIZENS = 8 ;
private static final int SDL_SYSTEM_CURSOR_SIZEALL = 9 ;
private static final int SDL_SYSTEM_CURSOR_NO = 10 ;
private static final int SDL_SYSTEM_CURSOR_HAND = 11 ;
protected static final int SDL_ORIENTATION_UNKNOWN = 0 ;
protected static final int SDL_ORIENTATION_LANDSCAPE = 1 ;
protected static final int SDL_ORIENTATION_LANDSCAPE_FLIPPED = 2 ;
protected static final int SDL_ORIENTATION_PORTRAIT = 3 ;
protected static final int SDL_ORIENTATION_PORTRAIT_FLIPPED = 4 ;
protected static int mCurrentOrientation ;
2021-04-20 23:40:33 +04:00
protected static Locale mCurrentLocale ;
2021-04-18 07:35:25 +04:00
// Handle the state of the native layer
public enum NativeState {
INIT , RESUMED , PAUSED
}
public static NativeState mNextNativeState ;
public static NativeState mCurrentNativeState ;
/** If shared libraries (e.g. SDL or the native application) could not be loaded. */
2021-04-20 23:40:33 +04:00
public static boolean mBrokenLibraries = true ;
2021-04-18 07:35:25 +04:00
// Main components
protected static SDLActivity mSingleton ;
protected static SDLSurface mSurface ;
protected static View mTextEdit ;
protected static boolean mScreenKeyboardShown ;
protected static ViewGroup mLayout ;
protected static SDLClipboardHandler mClipboardHandler ;
protected static Hashtable < Integer , PointerIcon > mCursors ;
protected static int mLastCursorID ;
protected static SDLGenericMotionListener_API12 mMotionListener ;
protected static HIDDeviceManager mHIDDeviceManager ;
// This is what SDL runs in. It invokes SDL_main(), eventually
protected static Thread mSDLThread ;
protected static SDLGenericMotionListener_API12 getMotionListener ( ) {
if ( mMotionListener = = null ) {
if ( Build . VERSION . SDK_INT > = 26 ) {
mMotionListener = new SDLGenericMotionListener_API26 ( ) ;
2021-04-20 23:40:33 +04:00
} else if ( Build . VERSION . SDK_INT > = 24 ) {
2021-04-18 07:35:25 +04:00
mMotionListener = new SDLGenericMotionListener_API24 ( ) ;
} else {
mMotionListener = new SDLGenericMotionListener_API12 ( ) ;
}
}
return mMotionListener ;
}
/ * *
* This method returns the name of the shared object with the application entry point
* It can be overridden by derived classes .
* /
protected String getMainSharedObject ( ) {
String library ;
String [ ] libraries = SDLActivity . mSingleton . getLibraries ( ) ;
if ( libraries . length > 0 ) {
library = " lib " + libraries [ libraries . length - 1 ] + " .so " ;
} else {
library = " libmain.so " ;
}
return getContext ( ) . getApplicationInfo ( ) . nativeLibraryDir + " / " + library ;
}
/ * *
* This method returns the name of the application entry point
* It can be overridden by derived classes .
* /
protected String getMainFunction ( ) {
return " SDL_main " ;
}
/ * *
* This method is called by SDL before loading the native shared libraries .
* It can be overridden to provide names of shared libraries to be loaded .
* The default implementation returns the defaults . It never returns null .
* An array returned by a new implementation must at least contain " SDL2 " .
* Also keep in mind that the order the libraries are loaded may matter .
* @return names of shared libraries to be loaded ( e . g . " SDL2 " , " main " ) .
* /
protected String [ ] getLibraries ( ) {
return new String [ ] {
" SDL2 " ,
// "SDL2_image",
// "SDL2_mixer",
// "SDL2_net",
// "SDL2_ttf",
" main "
} ;
}
// Load the .so
public void loadLibraries ( ) {
for ( String lib : getLibraries ( ) ) {
SDL . loadLibrary ( lib ) ;
}
}
/ * *
* This method is called by SDL before starting the native application thread .
* It can be overridden to provide the arguments after the application name .
* The default implementation returns an empty array . It never returns null .
* @return arguments for the native application .
* /
protected String [ ] getArguments ( ) {
return new String [ 0 ] ;
}
public static void initialize ( ) {
// The static nature of the singleton and Android quirkyness force us to initialize everything here
// Otherwise, when exiting the app and returning to it, these variables *keep* their pre exit values
mSingleton = null ;
mSurface = null ;
mTextEdit = null ;
mLayout = null ;
mClipboardHandler = null ;
mCursors = new Hashtable < Integer , PointerIcon > ( ) ;
mLastCursorID = 0 ;
mSDLThread = null ;
mIsResumedCalled = false ;
mHasFocus = true ;
mNextNativeState = NativeState . INIT ;
mCurrentNativeState = NativeState . INIT ;
}
// Setup
@Override
protected void onCreate ( Bundle savedInstanceState ) {
Log . v ( TAG , " Device: " + Build . DEVICE ) ;
Log . v ( TAG , " Model: " + Build . MODEL ) ;
Log . v ( TAG , " onCreate() " ) ;
super . onCreate ( savedInstanceState ) ;
try {
Thread . currentThread ( ) . setName ( " SDLActivity " ) ;
} catch ( Exception e ) {
Log . v ( TAG , " modify thread properties failed " + e . toString ( ) ) ;
}
// Load shared libraries
String errorMsgBrokenLib = " " ;
try {
loadLibraries ( ) ;
2021-04-20 23:40:33 +04:00
mBrokenLibraries = false ; /* success */
2021-04-18 07:35:25 +04:00
} catch ( UnsatisfiedLinkError e ) {
System . err . println ( e . getMessage ( ) ) ;
mBrokenLibraries = true ;
errorMsgBrokenLib = e . getMessage ( ) ;
} catch ( Exception e ) {
System . err . println ( e . getMessage ( ) ) ;
mBrokenLibraries = true ;
errorMsgBrokenLib = e . getMessage ( ) ;
}
if ( mBrokenLibraries )
{
mSingleton = this ;
AlertDialog . Builder dlgAlert = new AlertDialog . Builder ( this ) ;
dlgAlert . setMessage ( " An error occurred while trying to start the application. Please try again and/or reinstall. "
+ System . getProperty ( " line.separator " )
+ System . getProperty ( " line.separator " )
+ " Error: " + errorMsgBrokenLib ) ;
dlgAlert . setTitle ( " SDL Error " ) ;
dlgAlert . setPositiveButton ( " Exit " ,
new DialogInterface . OnClickListener ( ) {
@Override
public void onClick ( DialogInterface dialog , int id ) {
// if this button is clicked, close current activity
SDLActivity . mSingleton . finish ( ) ;
}
} ) ;
dlgAlert . setCancelable ( false ) ;
dlgAlert . create ( ) . show ( ) ;
return ;
}
// Set up JNI
SDL . setupJNI ( ) ;
// Initialize state
SDL . initialize ( ) ;
// So we can call stuff from static callbacks
mSingleton = this ;
SDL . setContext ( this ) ;
2021-04-20 23:40:33 +04:00
mClipboardHandler = new SDLClipboardHandler ( ) ;
2021-04-18 07:35:25 +04:00
mHIDDeviceManager = HIDDeviceManager . acquire ( this ) ;
// Set up the surface
mSurface = new SDLSurface ( getApplication ( ) ) ;
mLayout = new RelativeLayout ( this ) ;
mLayout . addView ( mSurface ) ;
// Get our current screen orientation and pass it down.
mCurrentOrientation = SDLActivity . getCurrentOrientation ( ) ;
// Only record current orientation
SDLActivity . onNativeOrientationChanged ( mCurrentOrientation ) ;
2021-04-20 23:40:33 +04:00
try {
if ( Build . VERSION . SDK_INT < 24 ) {
mCurrentLocale = getContext ( ) . getResources ( ) . getConfiguration ( ) . locale ;
} else {
mCurrentLocale = getContext ( ) . getResources ( ) . getConfiguration ( ) . getLocales ( ) . get ( 0 ) ;
}
} catch ( Exception ignored ) {
}
2021-04-18 07:35:25 +04:00
setContentView ( mLayout ) ;
setWindowStyle ( false ) ;
getWindow ( ) . getDecorView ( ) . setOnSystemUiVisibilityChangeListener ( this ) ;
// Get filename from "Open with" of another application
Intent intent = getIntent ( ) ;
if ( intent ! = null & & intent . getData ( ) ! = null ) {
String filename = intent . getData ( ) . getPath ( ) ;
if ( filename ! = null ) {
Log . v ( TAG , " Got filename: " + filename ) ;
SDLActivity . onNativeDropFile ( filename ) ;
}
}
}
protected void pauseNativeThread ( ) {
mNextNativeState = NativeState . PAUSED ;
mIsResumedCalled = false ;
if ( SDLActivity . mBrokenLibraries ) {
return ;
}
SDLActivity . handleNativeState ( ) ;
}
protected void resumeNativeThread ( ) {
mNextNativeState = NativeState . RESUMED ;
mIsResumedCalled = true ;
if ( SDLActivity . mBrokenLibraries ) {
return ;
}
SDLActivity . handleNativeState ( ) ;
}
// Events
@Override
protected void onPause ( ) {
Log . v ( TAG , " onPause() " ) ;
super . onPause ( ) ;
if ( mHIDDeviceManager ! = null ) {
mHIDDeviceManager . setFrozen ( true ) ;
}
if ( ! mHasMultiWindow ) {
pauseNativeThread ( ) ;
}
}
@Override
protected void onResume ( ) {
Log . v ( TAG , " onResume() " ) ;
super . onResume ( ) ;
if ( mHIDDeviceManager ! = null ) {
mHIDDeviceManager . setFrozen ( false ) ;
}
if ( ! mHasMultiWindow ) {
resumeNativeThread ( ) ;
}
}
@Override
protected void onStop ( ) {
Log . v ( TAG , " onStop() " ) ;
super . onStop ( ) ;
if ( mHasMultiWindow ) {
pauseNativeThread ( ) ;
}
}
@Override
protected void onStart ( ) {
Log . v ( TAG , " onStart() " ) ;
super . onStart ( ) ;
if ( mHasMultiWindow ) {
resumeNativeThread ( ) ;
}
}
public static int getCurrentOrientation ( ) {
int result = SDL_ORIENTATION_UNKNOWN ;
2021-12-07 05:20:09 +04:00
Activity activity = ( Activity ) getContext ( ) ;
if ( activity = = null ) {
return result ;
}
Display display = activity . getWindowManager ( ) . getDefaultDisplay ( ) ;
2021-04-18 07:35:25 +04:00
switch ( display . getRotation ( ) ) {
case Surface . ROTATION_0 :
result = SDL_ORIENTATION_PORTRAIT ;
break ;
case Surface . ROTATION_90 :
result = SDL_ORIENTATION_LANDSCAPE ;
break ;
case Surface . ROTATION_180 :
result = SDL_ORIENTATION_PORTRAIT_FLIPPED ;
break ;
case Surface . ROTATION_270 :
result = SDL_ORIENTATION_LANDSCAPE_FLIPPED ;
break ;
}
return result ;
}
@Override
public void onWindowFocusChanged ( boolean hasFocus ) {
super . onWindowFocusChanged ( hasFocus ) ;
Log . v ( TAG , " onWindowFocusChanged(): " + hasFocus ) ;
if ( SDLActivity . mBrokenLibraries ) {
return ;
}
mHasFocus = hasFocus ;
if ( hasFocus ) {
mNextNativeState = NativeState . RESUMED ;
SDLActivity . getMotionListener ( ) . reclaimRelativeMouseModeIfNeeded ( ) ;
SDLActivity . handleNativeState ( ) ;
nativeFocusChanged ( true ) ;
} else {
nativeFocusChanged ( false ) ;
if ( ! mHasMultiWindow ) {
mNextNativeState = NativeState . PAUSED ;
SDLActivity . handleNativeState ( ) ;
}
}
}
@Override
public void onLowMemory ( ) {
Log . v ( TAG , " onLowMemory() " ) ;
super . onLowMemory ( ) ;
if ( SDLActivity . mBrokenLibraries ) {
return ;
}
SDLActivity . nativeLowMemory ( ) ;
}
2021-04-20 23:40:33 +04:00
@Override
public void onConfigurationChanged ( Configuration newConfig ) {
Log . v ( TAG , " onConfigurationChanged() " ) ;
super . onConfigurationChanged ( newConfig ) ;
if ( SDLActivity . mBrokenLibraries ) {
return ;
}
if ( mCurrentLocale = = null | | ! mCurrentLocale . equals ( newConfig . locale ) ) {
mCurrentLocale = newConfig . locale ;
SDLActivity . onNativeLocaleChanged ( ) ;
}
}
2021-04-18 07:35:25 +04:00
@Override
protected void onDestroy ( ) {
Log . v ( TAG , " onDestroy() " ) ;
if ( mHIDDeviceManager ! = null ) {
HIDDeviceManager . release ( mHIDDeviceManager ) ;
mHIDDeviceManager = null ;
}
if ( SDLActivity . mBrokenLibraries ) {
super . onDestroy ( ) ;
return ;
}
if ( SDLActivity . mSDLThread ! = null ) {
// Send Quit event to "SDLThread" thread
SDLActivity . nativeSendQuit ( ) ;
// Wait for "SDLThread" thread to end
try {
SDLActivity . mSDLThread . join ( ) ;
} catch ( Exception e ) {
Log . v ( TAG , " Problem stopping SDLThread: " + e ) ;
}
}
SDLActivity . nativeQuit ( ) ;
super . onDestroy ( ) ;
}
@Override
public void onBackPressed ( ) {
// Check if we want to block the back button in case of mouse right click.
//
// If we do, the normal hardware back button will no longer work and people have to use home,
// but the mouse right click will work.
//
2021-12-07 05:20:09 +04:00
boolean trapBack = SDLActivity . nativeGetHintBoolean ( " SDL_ANDROID_TRAP_BACK_BUTTON " , false ) ;
if ( trapBack ) {
2021-04-18 07:35:25 +04:00
// Exit and let the mouse handler handle this button (if appropriate)
return ;
}
// Default system back button behavior.
if ( ! isFinishing ( ) ) {
super . onBackPressed ( ) ;
}
}
// Called by JNI from SDL.
public static void manualBackButton ( ) {
mSingleton . pressBackButton ( ) ;
}
// Used to get us onto the activity's main thread
public void pressBackButton ( ) {
runOnUiThread ( new Runnable ( ) {
@Override
public void run ( ) {
if ( ! SDLActivity . this . isFinishing ( ) ) {
SDLActivity . this . superOnBackPressed ( ) ;
}
}
} ) ;
}
// Used to access the system back behavior.
public void superOnBackPressed ( ) {
super . onBackPressed ( ) ;
}
@Override
public boolean dispatchKeyEvent ( KeyEvent event ) {
if ( SDLActivity . mBrokenLibraries ) {
return false ;
}
int keyCode = event . getKeyCode ( ) ;
// Ignore certain special keys so they're handled by Android
if ( keyCode = = KeyEvent . KEYCODE_VOLUME_DOWN | |
keyCode = = KeyEvent . KEYCODE_VOLUME_UP | |
keyCode = = KeyEvent . KEYCODE_CAMERA | |
keyCode = = KeyEvent . KEYCODE_ZOOM_IN | | /* API 11 */
keyCode = = KeyEvent . KEYCODE_ZOOM_OUT /* API 11 */
) {
return false ;
}
return super . dispatchKeyEvent ( event ) ;
}
/* Transition to next state */
public static void handleNativeState ( ) {
if ( mNextNativeState = = mCurrentNativeState ) {
// Already in same state, discard.
return ;
}
// Try a transition to init state
if ( mNextNativeState = = NativeState . INIT ) {
mCurrentNativeState = mNextNativeState ;
return ;
}
// Try a transition to paused state
if ( mNextNativeState = = NativeState . PAUSED ) {
if ( mSDLThread ! = null ) {
nativePause ( ) ;
}
if ( mSurface ! = null ) {
mSurface . handlePause ( ) ;
}
mCurrentNativeState = mNextNativeState ;
return ;
}
// Try a transition to resumed state
if ( mNextNativeState = = NativeState . RESUMED ) {
if ( mSurface . mIsSurfaceReady & & mHasFocus & & mIsResumedCalled ) {
if ( mSDLThread = = null ) {
// This is the entry point to the C app.
// Start up the C app thread and enable sensor input for the first time
// FIXME: Why aren't we enabling sensor input at start?
mSDLThread = new Thread ( new SDLMain ( ) , " SDLThread " ) ;
mSurface . enableSensor ( Sensor . TYPE_ACCELEROMETER , true ) ;
mSDLThread . start ( ) ;
// No nativeResume(), don't signal Android_ResumeSem
} else {
nativeResume ( ) ;
}
2021-04-20 23:40:33 +04:00
mSurface . handleResume ( ) ;
2021-04-18 07:35:25 +04:00
mCurrentNativeState = mNextNativeState ;
}
}
}
// Messages from the SDLMain thread
static final int COMMAND_CHANGE_TITLE = 1 ;
static final int COMMAND_CHANGE_WINDOW_STYLE = 2 ;
static final int COMMAND_TEXTEDIT_HIDE = 3 ;
static final int COMMAND_SET_KEEP_SCREEN_ON = 5 ;
protected static final int COMMAND_USER = 0x8000 ;
protected static boolean mFullscreenModeActive ;
/ * *
* This method is called by SDL if SDL did not handle a message itself .
* This happens if a received message contains an unsupported command .
* Method can be overwritten to handle Messages in a different class .
* @param command the command of the message .
* @param param the parameter of the message . May be null .
* @return if the message was handled in overridden method .
* /
protected boolean onUnhandledMessage ( int command , Object param ) {
return false ;
}
/ * *
* A Handler class for Messages from native SDL applications .
* It uses current Activities as target ( e . g . for the title ) .
* static to prevent implicit references to enclosing object .
* /
protected static class SDLCommandHandler extends Handler {
@Override
public void handleMessage ( Message msg ) {
Context context = SDL . getContext ( ) ;
if ( context = = null ) {
Log . e ( TAG , " error handling message, getContext() returned null " ) ;
return ;
}
switch ( msg . arg1 ) {
case COMMAND_CHANGE_TITLE :
if ( context instanceof Activity ) {
( ( Activity ) context ) . setTitle ( ( String ) msg . obj ) ;
} else {
Log . e ( TAG , " error handling message, getContext() returned no Activity " ) ;
}
break ;
case COMMAND_CHANGE_WINDOW_STYLE :
2021-04-20 23:40:33 +04:00
if ( Build . VERSION . SDK_INT > = 19 ) {
if ( context instanceof Activity ) {
Window window = ( ( Activity ) context ) . getWindow ( ) ;
if ( window ! = null ) {
if ( ( msg . obj instanceof Integer ) & & ( ( Integer ) msg . obj ! = 0 ) ) {
int flags = View . SYSTEM_UI_FLAG_FULLSCREEN |
2021-04-18 07:35:25 +04:00
View . SYSTEM_UI_FLAG_HIDE_NAVIGATION |
View . SYSTEM_UI_FLAG_IMMERSIVE_STICKY |
View . SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
View . SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
View . SYSTEM_UI_FLAG_LAYOUT_STABLE | View . INVISIBLE ;
2021-04-20 23:40:33 +04:00
window . getDecorView ( ) . setSystemUiVisibility ( flags ) ;
window . addFlags ( WindowManager . LayoutParams . FLAG_FULLSCREEN ) ;
window . clearFlags ( WindowManager . LayoutParams . FLAG_FORCE_NOT_FULLSCREEN ) ;
SDLActivity . mFullscreenModeActive = true ;
} else {
int flags = View . SYSTEM_UI_FLAG_LAYOUT_STABLE | View . SYSTEM_UI_FLAG_VISIBLE ;
window . getDecorView ( ) . setSystemUiVisibility ( flags ) ;
window . addFlags ( WindowManager . LayoutParams . FLAG_FORCE_NOT_FULLSCREEN ) ;
window . clearFlags ( WindowManager . LayoutParams . FLAG_FULLSCREEN ) ;
SDLActivity . mFullscreenModeActive = false ;
}
2021-04-18 07:35:25 +04:00
}
2021-04-20 23:40:33 +04:00
} else {
Log . e ( TAG , " error handling message, getContext() returned no Activity " ) ;
2021-04-18 07:35:25 +04:00
}
}
break ;
case COMMAND_TEXTEDIT_HIDE :
if ( mTextEdit ! = null ) {
// Note: On some devices setting view to GONE creates a flicker in landscape.
// Setting the View's sizes to 0 is similar to GONE but without the flicker.
// The sizes will be set to useful values when the keyboard is shown again.
mTextEdit . setLayoutParams ( new RelativeLayout . LayoutParams ( 0 , 0 ) ) ;
InputMethodManager imm = ( InputMethodManager ) context . getSystemService ( Context . INPUT_METHOD_SERVICE ) ;
imm . hideSoftInputFromWindow ( mTextEdit . getWindowToken ( ) , 0 ) ;
mScreenKeyboardShown = false ;
mSurface . requestFocus ( ) ;
}
break ;
case COMMAND_SET_KEEP_SCREEN_ON :
{
if ( context instanceof Activity ) {
Window window = ( ( Activity ) context ) . getWindow ( ) ;
if ( window ! = null ) {
2021-04-20 23:40:33 +04:00
if ( ( msg . obj instanceof Integer ) & & ( ( Integer ) msg . obj ! = 0 ) ) {
2021-04-18 07:35:25 +04:00
window . addFlags ( WindowManager . LayoutParams . FLAG_KEEP_SCREEN_ON ) ;
} else {
window . clearFlags ( WindowManager . LayoutParams . FLAG_KEEP_SCREEN_ON ) ;
}
}
}
break ;
}
default :
if ( ( context instanceof SDLActivity ) & & ! ( ( SDLActivity ) context ) . onUnhandledMessage ( msg . arg1 , msg . obj ) ) {
Log . e ( TAG , " error handling message, command is " + msg . arg1 ) ;
}
}
}
}
// Handler for the messages
Handler commandHandler = new SDLCommandHandler ( ) ;
// Send a message from the SDLMain thread
boolean sendCommand ( int command , Object data ) {
Message msg = commandHandler . obtainMessage ( ) ;
msg . arg1 = command ;
msg . obj = data ;
boolean result = commandHandler . sendMessage ( msg ) ;
2021-04-20 23:40:33 +04:00
if ( Build . VERSION . SDK_INT > = 19 ) {
if ( command = = COMMAND_CHANGE_WINDOW_STYLE ) {
// Ensure we don't return until the resize has actually happened,
// or 500ms have passed.
boolean bShouldWait = false ;
if ( data instanceof Integer ) {
// Let's figure out if we're already laid out fullscreen or not.
Display display = ( ( WindowManager ) getSystemService ( Context . WINDOW_SERVICE ) ) . getDefaultDisplay ( ) ;
DisplayMetrics realMetrics = new DisplayMetrics ( ) ;
display . getRealMetrics ( realMetrics ) ;
boolean bFullscreenLayout = ( ( realMetrics . widthPixels = = mSurface . getWidth ( ) ) & &
( realMetrics . heightPixels = = mSurface . getHeight ( ) ) ) ;
if ( ( Integer ) data = = 1 ) {
// If we aren't laid out fullscreen or actively in fullscreen mode already, we're going
// to change size and should wait for surfaceChanged() before we return, so the size
// is right back in native code. If we're already laid out fullscreen, though, we're
// not going to change size even if we change decor modes, so we shouldn't wait for
// surfaceChanged() -- which may not even happen -- and should return immediately.
bShouldWait = ! bFullscreenLayout ;
} else {
// If we're laid out fullscreen (even if the status bar and nav bar are present),
// or are actively in fullscreen, we're going to change size and should wait for
// surfaceChanged before we return, so the size is right back in native code.
bShouldWait = bFullscreenLayout ;
}
2021-04-18 07:35:25 +04:00
}
2021-04-20 23:40:33 +04:00
if ( bShouldWait & & ( SDLActivity . getContext ( ) ! = null ) ) {
// We'll wait for the surfaceChanged() method, which will notify us
// when called. That way, we know our current size is really the
// size we need, instead of grabbing a size that's still got
// the navigation and/or status bars before they're hidden.
//
// We'll wait for up to half a second, because some devices
// take a surprisingly long time for the surface resize, but
// then we'll just give up and return.
//
synchronized ( SDLActivity . getContext ( ) ) {
try {
SDLActivity . getContext ( ) . wait ( 500 ) ;
} catch ( InterruptedException ie ) {
ie . printStackTrace ( ) ;
}
2021-04-18 07:35:25 +04:00
}
}
}
}
return result ;
}
// C functions we call
public static native int nativeSetupJNI ( ) ;
public static native int nativeRunMain ( String library , String function , Object arguments ) ;
public static native void nativeLowMemory ( ) ;
public static native void nativeSendQuit ( ) ;
public static native void nativeQuit ( ) ;
public static native void nativePause ( ) ;
public static native void nativeResume ( ) ;
public static native void nativeFocusChanged ( boolean hasFocus ) ;
public static native void onNativeDropFile ( String filename ) ;
2021-05-09 13:30:38 +04:00
public static native void nativeSetScreenResolution ( int surfaceWidth , int surfaceHeight , int deviceWidth , int deviceHeight , float rate ) ;
2021-04-18 07:35:25 +04:00
public static native void onNativeResize ( ) ;
public static native void onNativeKeyDown ( int keycode ) ;
public static native void onNativeKeyUp ( int keycode ) ;
public static native boolean onNativeSoftReturnKey ( ) ;
public static native void onNativeKeyboardFocusLost ( ) ;
public static native void onNativeMouse ( int button , int action , float x , float y , boolean relative ) ;
public static native void onNativeTouch ( int touchDevId , int pointerFingerId ,
int action , float x ,
float y , float p ) ;
public static native void onNativeAccel ( float x , float y , float z ) ;
public static native void onNativeClipboardChanged ( ) ;
public static native void onNativeSurfaceCreated ( ) ;
public static native void onNativeSurfaceChanged ( ) ;
public static native void onNativeSurfaceDestroyed ( ) ;
public static native String nativeGetHint ( String name ) ;
2021-12-07 05:20:09 +04:00
public static native boolean nativeGetHintBoolean ( String name , boolean default_value ) ;
2021-04-18 07:35:25 +04:00
public static native void nativeSetenv ( String name , String value ) ;
public static native void onNativeOrientationChanged ( int orientation ) ;
public static native void nativeAddTouch ( int touchId , String name ) ;
public static native void nativePermissionResult ( int requestCode , boolean result ) ;
2021-04-20 23:40:33 +04:00
public static native void onNativeLocaleChanged ( ) ;
2021-04-18 07:35:25 +04:00
/ * *
* This method is called by SDL using JNI .
* /
public static boolean setActivityTitle ( String title ) {
// Called from SDLMain() thread and can't directly affect the view
return mSingleton . sendCommand ( COMMAND_CHANGE_TITLE , title ) ;
}
/ * *
* This method is called by SDL using JNI .
* /
public static void setWindowStyle ( boolean fullscreen ) {
// Called from SDLMain() thread and can't directly affect the view
mSingleton . sendCommand ( COMMAND_CHANGE_WINDOW_STYLE , fullscreen ? 1 : 0 ) ;
}
/ * *
* This method is called by SDL using JNI .
* This is a static method for JNI convenience , it calls a non - static method
* so that is can be overridden
* /
public static void setOrientation ( int w , int h , boolean resizable , String hint )
{
if ( mSingleton ! = null ) {
mSingleton . setOrientationBis ( w , h , resizable , hint ) ;
}
}
/ * *
* This can be overridden
* /
public void setOrientationBis ( int w , int h , boolean resizable , String hint )
{
int orientation_landscape = - 1 ;
int orientation_portrait = - 1 ;
/* If set, hint "explicitly controls which UI orientations are allowed". */
if ( hint . contains ( " LandscapeRight " ) & & hint . contains ( " LandscapeLeft " ) ) {
orientation_landscape = ActivityInfo . SCREEN_ORIENTATION_SENSOR_LANDSCAPE ;
} else if ( hint . contains ( " LandscapeRight " ) ) {
orientation_landscape = ActivityInfo . SCREEN_ORIENTATION_LANDSCAPE ;
} else if ( hint . contains ( " LandscapeLeft " ) ) {
orientation_landscape = ActivityInfo . SCREEN_ORIENTATION_REVERSE_LANDSCAPE ;
}
if ( hint . contains ( " Portrait " ) & & hint . contains ( " PortraitUpsideDown " ) ) {
orientation_portrait = ActivityInfo . SCREEN_ORIENTATION_SENSOR_PORTRAIT ;
} else if ( hint . contains ( " Portrait " ) ) {
orientation_portrait = ActivityInfo . SCREEN_ORIENTATION_PORTRAIT ;
} else if ( hint . contains ( " PortraitUpsideDown " ) ) {
orientation_portrait = ActivityInfo . SCREEN_ORIENTATION_REVERSE_PORTRAIT ;
}
2021-04-20 23:40:33 +04:00
boolean is_landscape_allowed = ( orientation_landscape ! = - 1 ) ;
boolean is_portrait_allowed = ( orientation_portrait ! = - 1 ) ;
int req ; /* Requested orientation */
2021-04-18 07:35:25 +04:00
/* No valid hint, nothing is explicitly allowed */
if ( ! is_portrait_allowed & & ! is_landscape_allowed ) {
if ( resizable ) {
/* All orientations are allowed */
req = ActivityInfo . SCREEN_ORIENTATION_FULL_SENSOR ;
} else {
/* Fixed window and nothing specified. Get orientation from w/h of created window */
req = ( w > h ? ActivityInfo . SCREEN_ORIENTATION_SENSOR_LANDSCAPE : ActivityInfo . SCREEN_ORIENTATION_SENSOR_PORTRAIT ) ;
}
} else {
/* At least one orientation is allowed */
if ( resizable ) {
if ( is_portrait_allowed & & is_landscape_allowed ) {
/* hint allows both landscape and portrait, promote to full sensor */
req = ActivityInfo . SCREEN_ORIENTATION_FULL_SENSOR ;
} else {
/* Use the only one allowed "orientation" */
req = ( is_landscape_allowed ? orientation_landscape : orientation_portrait ) ;
}
} else {
/* Fixed window and both orientations are allowed. Choose one. */
if ( is_portrait_allowed & & is_landscape_allowed ) {
req = ( w > h ? orientation_landscape : orientation_portrait ) ;
} else {
/* Use the only one allowed "orientation" */
req = ( is_landscape_allowed ? orientation_landscape : orientation_portrait ) ;
}
}
}
2021-04-20 23:40:33 +04:00
Log . v ( TAG , " setOrientation() requestedOrientation= " + req + " width= " + w + " height= " + h + " resizable= " + resizable + " hint= " + hint ) ;
2021-04-18 07:35:25 +04:00
mSingleton . setRequestedOrientation ( req ) ;
}
/ * *
* This method is called by SDL using JNI .
* /
public static void minimizeWindow ( ) {
if ( mSingleton = = null ) {
return ;
}
Intent startMain = new Intent ( Intent . ACTION_MAIN ) ;
startMain . addCategory ( Intent . CATEGORY_HOME ) ;
startMain . setFlags ( Intent . FLAG_ACTIVITY_NEW_TASK ) ;
mSingleton . startActivity ( startMain ) ;
}
/ * *
* This method is called by SDL using JNI .
* /
public static boolean shouldMinimizeOnFocusLoss ( ) {
/ *
if ( Build . VERSION . SDK_INT > = 24 ) {
if ( mSingleton = = null ) {
return true ;
}
if ( mSingleton . isInMultiWindowMode ( ) ) {
return false ;
}
if ( mSingleton . isInPictureInPictureMode ( ) ) {
return false ;
}
}
return true ;
* /
return false ;
}
/ * *
* This method is called by SDL using JNI .
* /
public static boolean isScreenKeyboardShown ( )
{
if ( mTextEdit = = null ) {
return false ;
}
if ( ! mScreenKeyboardShown ) {
return false ;
}
InputMethodManager imm = ( InputMethodManager ) SDL . getContext ( ) . getSystemService ( Context . INPUT_METHOD_SERVICE ) ;
return imm . isAcceptingText ( ) ;
}
/ * *
* This method is called by SDL using JNI .
* /
public static boolean supportsRelativeMouse ( )
{
// DeX mode in Samsung Experience 9.0 and earlier doesn't support relative mice properly under
// Android 7 APIs, and simply returns no data under Android 8 APIs.
//
// This is fixed in Samsung Experience 9.5, which corresponds to Android 8.1.0, and
// thus SDK version 27. If we are in DeX mode and not API 27 or higher, as a result,
// we should stick to relative mode.
//
if ( ( Build . VERSION . SDK_INT < 27 ) & & isDeXMode ( ) ) {
return false ;
}
return SDLActivity . getMotionListener ( ) . supportsRelativeMouse ( ) ;
}
/ * *
* This method is called by SDL using JNI .
* /
public static boolean setRelativeMouseEnabled ( boolean enabled )
{
if ( enabled & & ! supportsRelativeMouse ( ) ) {
return false ;
}
return SDLActivity . getMotionListener ( ) . setRelativeMouseEnabled ( enabled ) ;
}
/ * *
* This method is called by SDL using JNI .
* /
public static boolean sendMessage ( int command , int param ) {
if ( mSingleton = = null ) {
return false ;
}
2021-04-20 23:40:33 +04:00
return mSingleton . sendCommand ( command , param ) ;
2021-04-18 07:35:25 +04:00
}
/ * *
* This method is called by SDL using JNI .
* /
public static Context getContext ( ) {
return SDL . getContext ( ) ;
}
/ * *
* This method is called by SDL using JNI .
* /
public static boolean isAndroidTV ( ) {
UiModeManager uiModeManager = ( UiModeManager ) getContext ( ) . getSystemService ( UI_MODE_SERVICE ) ;
if ( uiModeManager . getCurrentModeType ( ) = = Configuration . UI_MODE_TYPE_TELEVISION ) {
return true ;
}
if ( Build . MANUFACTURER . equals ( " MINIX " ) & & Build . MODEL . equals ( " NEO-U1 " ) ) {
return true ;
}
if ( Build . MANUFACTURER . equals ( " Amlogic " ) & & Build . MODEL . equals ( " X96-W " ) ) {
return true ;
}
2021-04-20 23:40:33 +04:00
return Build . MANUFACTURER . equals ( " Amlogic " ) & & Build . MODEL . startsWith ( " TV " ) ;
2021-04-18 07:35:25 +04:00
}
2021-04-20 23:40:33 +04:00
public static double getDiagonal ( )
{
2021-04-18 07:35:25 +04:00
DisplayMetrics metrics = new DisplayMetrics ( ) ;
Activity activity = ( Activity ) getContext ( ) ;
if ( activity = = null ) {
2021-04-20 23:40:33 +04:00
return 0 . 0 ;
2021-04-18 07:35:25 +04:00
}
activity . getWindowManager ( ) . getDefaultDisplay ( ) . getMetrics ( metrics ) ;
double dWidthInches = metrics . widthPixels / ( double ) metrics . xdpi ;
double dHeightInches = metrics . heightPixels / ( double ) metrics . ydpi ;
2021-04-20 23:40:33 +04:00
return Math . sqrt ( ( dWidthInches * dWidthInches ) + ( dHeightInches * dHeightInches ) ) ;
}
2021-04-18 07:35:25 +04:00
2021-04-20 23:40:33 +04:00
/ * *
* This method is called by SDL using JNI .
* /
public static boolean isTablet ( ) {
2021-04-18 07:35:25 +04:00
// If our diagonal size is seven inches or greater, we consider ourselves a tablet.
2021-04-20 23:40:33 +04:00
return ( getDiagonal ( ) > = 7 . 0 ) ;
2021-04-18 07:35:25 +04:00
}
/ * *
* This method is called by SDL using JNI .
* /
public static boolean isChromebook ( ) {
if ( getContext ( ) = = null ) {
return false ;
}
return getContext ( ) . getPackageManager ( ) . hasSystemFeature ( " org.chromium.arc.device_management " ) ;
}
/ * *
* This method is called by SDL using JNI .
* /
public static boolean isDeXMode ( ) {
if ( Build . VERSION . SDK_INT < 24 ) {
return false ;
}
try {
final Configuration config = getContext ( ) . getResources ( ) . getConfiguration ( ) ;
2021-04-20 23:40:33 +04:00
final Class < ? > configClass = config . getClass ( ) ;
2021-04-18 07:35:25 +04:00
return configClass . getField ( " SEM_DESKTOP_MODE_ENABLED " ) . getInt ( configClass )
= = configClass . getField ( " semDesktopModeEnabled " ) . getInt ( config ) ;
} catch ( Exception ignored ) {
return false ;
}
}
/ * *
* This method is called by SDL using JNI .
* /
public static DisplayMetrics getDisplayDPI ( ) {
return getContext ( ) . getResources ( ) . getDisplayMetrics ( ) ;
}
/ * *
* This method is called by SDL using JNI .
* /
public static boolean getManifestEnvironmentVariables ( ) {
try {
2021-04-20 23:40:33 +04:00
if ( getContext ( ) = = null ) {
return false ;
}
2021-04-18 07:35:25 +04:00
ApplicationInfo applicationInfo = getContext ( ) . getPackageManager ( ) . getApplicationInfo ( getContext ( ) . getPackageName ( ) , PackageManager . GET_META_DATA ) ;
Bundle bundle = applicationInfo . metaData ;
if ( bundle = = null ) {
return false ;
}
String prefix = " SDL_ENV. " ;
final int trimLength = prefix . length ( ) ;
for ( String key : bundle . keySet ( ) ) {
if ( key . startsWith ( prefix ) ) {
String name = key . substring ( trimLength ) ;
String value = bundle . get ( key ) . toString ( ) ;
nativeSetenv ( name , value ) ;
}
}
/* environment variables set! */
return true ;
} catch ( Exception e ) {
2021-04-20 23:40:33 +04:00
Log . v ( TAG , " exception " + e . toString ( ) ) ;
2021-04-18 07:35:25 +04:00
}
return false ;
}
// This method is called by SDLControllerManager's API 26 Generic Motion Handler.
public static View getContentView ( )
{
2021-04-20 23:40:33 +04:00
return mLayout ;
2021-04-18 07:35:25 +04:00
}
static class ShowTextInputTask implements Runnable {
/ *
* This is used to regulate the pan & scan method to have some offset from
* the bottom edge of the input region and the top edge of an input
* method ( soft keyboard )
* /
static final int HEIGHT_PADDING = 15 ;
public int x , y , w , h ;
public ShowTextInputTask ( int x , int y , int w , int h ) {
this . x = x ;
this . y = y ;
this . w = w ;
this . h = h ;
/* Minimum size of 1 pixel, so it takes focus. */
if ( this . w < = 0 ) {
this . w = 1 ;
}
if ( this . h + HEIGHT_PADDING < = 0 ) {
this . h = 1 - HEIGHT_PADDING ;
}
}
@Override
public void run ( ) {
RelativeLayout . LayoutParams params = new RelativeLayout . LayoutParams ( w , h + HEIGHT_PADDING ) ;
params . leftMargin = x ;
params . topMargin = y ;
if ( mTextEdit = = null ) {
mTextEdit = new DummyEdit ( SDL . getContext ( ) ) ;
mLayout . addView ( mTextEdit , params ) ;
} else {
mTextEdit . setLayoutParams ( params ) ;
}
mTextEdit . setVisibility ( View . VISIBLE ) ;
mTextEdit . requestFocus ( ) ;
InputMethodManager imm = ( InputMethodManager ) SDL . getContext ( ) . getSystemService ( Context . INPUT_METHOD_SERVICE ) ;
imm . showSoftInput ( mTextEdit , 0 ) ;
mScreenKeyboardShown = true ;
}
}
/ * *
* This method is called by SDL using JNI .
* /
public static boolean showTextInput ( int x , int y , int w , int h ) {
// Transfer the task to the main thread as a Runnable
return mSingleton . commandHandler . post ( new ShowTextInputTask ( x , y , w , h ) ) ;
}
public static boolean isTextInputEvent ( KeyEvent event ) {
// Key pressed with Ctrl should be sent as SDL_KEYDOWN/SDL_KEYUP and not SDL_TEXTINPUT
if ( event . isCtrlPressed ( ) ) {
return false ;
}
return event . isPrintingKey ( ) | | event . getKeyCode ( ) = = KeyEvent . KEYCODE_SPACE ;
}
/ * *
* This method is called by SDL using JNI .
* /
public static Surface getNativeSurface ( ) {
if ( SDLActivity . mSurface = = null ) {
return null ;
}
return SDLActivity . mSurface . getNativeSurface ( ) ;
}
// Input
/ * *
* This method is called by SDL using JNI .
* /
public static void initTouch ( ) {
int [ ] ids = InputDevice . getDeviceIds ( ) ;
2021-04-20 23:40:33 +04:00
for ( int id : ids ) {
InputDevice device = InputDevice . getDevice ( id ) ;
2022-07-15 06:00:50 +04:00
/* Allow SOURCE_TOUCHSCREEN and also Virtual InputDevices because they can send TOUCHSCREEN events */
if ( device ! = null & & ( ( device . getSources ( ) & InputDevice . SOURCE_TOUCHSCREEN ) = = InputDevice . SOURCE_TOUCHSCREEN
| | device . isVirtual ( ) ) ) {
int touchDevId = device . getId ( ) ;
/ *
* Prevent id to be - 1 , since it ' s used in SDL internal for synthetic events
* Appears when using Android emulator , eg :
* adb shell input mouse tap 100 100
* adb shell input touchscreen tap 100 100
* /
if ( touchDevId < 0 ) {
touchDevId - = 1 ;
}
nativeAddTouch ( touchDevId , device . getName ( ) ) ;
2021-04-18 07:35:25 +04:00
}
}
}
// Messagebox
/** Result of current messagebox. Also used for blocking the calling thread. */
protected final int [ ] messageboxSelection = new int [ 1 ] ;
/ * *
* This method is called by SDL using JNI .
* Shows the messagebox from UI thread and block calling thread .
* buttonFlags , buttonIds and buttonTexts must have same length .
* @param buttonFlags array containing flags for every button .
* @param buttonIds array containing id for every button .
* @param buttonTexts array containing text for every button .
* @param colors null for default or array of length 5 containing colors .
* @return button id or - 1 .
* /
public int messageboxShowMessageBox (
final int flags ,
final String title ,
final String message ,
final int [ ] buttonFlags ,
final int [ ] buttonIds ,
final String [ ] buttonTexts ,
final int [ ] colors ) {
messageboxSelection [ 0 ] = - 1 ;
// sanity checks
if ( ( buttonFlags . length ! = buttonIds . length ) & & ( buttonIds . length ! = buttonTexts . length ) ) {
return - 1 ; // implementation broken
}
// collect arguments for Dialog
final Bundle args = new Bundle ( ) ;
args . putInt ( " flags " , flags ) ;
args . putString ( " title " , title ) ;
args . putString ( " message " , message ) ;
args . putIntArray ( " buttonFlags " , buttonFlags ) ;
args . putIntArray ( " buttonIds " , buttonIds ) ;
args . putStringArray ( " buttonTexts " , buttonTexts ) ;
args . putIntArray ( " colors " , colors ) ;
// trigger Dialog creation on UI thread
runOnUiThread ( new Runnable ( ) {
@Override
public void run ( ) {
2021-04-20 23:40:33 +04:00
messageboxCreateAndShow ( args ) ;
2021-04-18 07:35:25 +04:00
}
} ) ;
// block the calling thread
synchronized ( messageboxSelection ) {
try {
messageboxSelection . wait ( ) ;
} catch ( InterruptedException ex ) {
ex . printStackTrace ( ) ;
return - 1 ;
}
}
// return selected value
return messageboxSelection [ 0 ] ;
}
2021-04-20 23:40:33 +04:00
protected void messageboxCreateAndShow ( Bundle args ) {
2021-04-18 07:35:25 +04:00
// TODO set values from "flags" to messagebox dialog
// get colors
int [ ] colors = args . getIntArray ( " colors " ) ;
int backgroundColor ;
int textColor ;
int buttonBorderColor ;
int buttonBackgroundColor ;
int buttonSelectedColor ;
if ( colors ! = null ) {
int i = - 1 ;
backgroundColor = colors [ + + i ] ;
textColor = colors [ + + i ] ;
buttonBorderColor = colors [ + + i ] ;
buttonBackgroundColor = colors [ + + i ] ;
buttonSelectedColor = colors [ + + i ] ;
} else {
backgroundColor = Color . TRANSPARENT ;
textColor = Color . TRANSPARENT ;
buttonBorderColor = Color . TRANSPARENT ;
buttonBackgroundColor = Color . TRANSPARENT ;
buttonSelectedColor = Color . TRANSPARENT ;
}
// create dialog with title and a listener to wake up calling thread
2021-04-20 23:40:33 +04:00
final AlertDialog dialog = new AlertDialog . Builder ( this ) . create ( ) ;
2021-04-18 07:35:25 +04:00
dialog . setTitle ( args . getString ( " title " ) ) ;
dialog . setCancelable ( false ) ;
dialog . setOnDismissListener ( new DialogInterface . OnDismissListener ( ) {
@Override
public void onDismiss ( DialogInterface unused ) {
synchronized ( messageboxSelection ) {
messageboxSelection . notify ( ) ;
}
}
} ) ;
// create text
TextView message = new TextView ( this ) ;
message . setGravity ( Gravity . CENTER ) ;
message . setText ( args . getString ( " message " ) ) ;
if ( textColor ! = Color . TRANSPARENT ) {
message . setTextColor ( textColor ) ;
}
// create buttons
int [ ] buttonFlags = args . getIntArray ( " buttonFlags " ) ;
int [ ] buttonIds = args . getIntArray ( " buttonIds " ) ;
String [ ] buttonTexts = args . getStringArray ( " buttonTexts " ) ;
final SparseArray < Button > mapping = new SparseArray < Button > ( ) ;
LinearLayout buttons = new LinearLayout ( this ) ;
buttons . setOrientation ( LinearLayout . HORIZONTAL ) ;
buttons . setGravity ( Gravity . CENTER ) ;
for ( int i = 0 ; i < buttonTexts . length ; + + i ) {
Button button = new Button ( this ) ;
final int id = buttonIds [ i ] ;
button . setOnClickListener ( new View . OnClickListener ( ) {
@Override
public void onClick ( View v ) {
messageboxSelection [ 0 ] = id ;
dialog . dismiss ( ) ;
}
} ) ;
if ( buttonFlags [ i ] ! = 0 ) {
// see SDL_messagebox.h
if ( ( buttonFlags [ i ] & 0x00000001 ) ! = 0 ) {
mapping . put ( KeyEvent . KEYCODE_ENTER , button ) ;
}
if ( ( buttonFlags [ i ] & 0x00000002 ) ! = 0 ) {
mapping . put ( KeyEvent . KEYCODE_ESCAPE , button ) ; /* API 11 */
}
}
button . setText ( buttonTexts [ i ] ) ;
if ( textColor ! = Color . TRANSPARENT ) {
button . setTextColor ( textColor ) ;
}
if ( buttonBorderColor ! = Color . TRANSPARENT ) {
// TODO set color for border of messagebox button
}
if ( buttonBackgroundColor ! = Color . TRANSPARENT ) {
Drawable drawable = button . getBackground ( ) ;
if ( drawable = = null ) {
// setting the color this way removes the style
button . setBackgroundColor ( buttonBackgroundColor ) ;
} else {
// setting the color this way keeps the style (gradient, padding, etc.)
drawable . setColorFilter ( buttonBackgroundColor , PorterDuff . Mode . MULTIPLY ) ;
}
}
if ( buttonSelectedColor ! = Color . TRANSPARENT ) {
// TODO set color for selected messagebox button
}
buttons . addView ( button ) ;
}
// create content
LinearLayout content = new LinearLayout ( this ) ;
content . setOrientation ( LinearLayout . VERTICAL ) ;
content . addView ( message ) ;
content . addView ( buttons ) ;
if ( backgroundColor ! = Color . TRANSPARENT ) {
content . setBackgroundColor ( backgroundColor ) ;
}
// add content to dialog and return
2021-04-20 23:40:33 +04:00
dialog . setView ( content ) ;
2021-04-18 07:35:25 +04:00
dialog . setOnKeyListener ( new Dialog . OnKeyListener ( ) {
@Override
public boolean onKey ( DialogInterface d , int keyCode , KeyEvent event ) {
Button button = mapping . get ( keyCode ) ;
if ( button ! = null ) {
if ( event . getAction ( ) = = KeyEvent . ACTION_UP ) {
button . performClick ( ) ;
}
return true ; // also for ignored actions
}
return false ;
}
} ) ;
2021-04-20 23:40:33 +04:00
dialog . show ( ) ;
2021-04-18 07:35:25 +04:00
}
private final Runnable rehideSystemUi = new Runnable ( ) {
@Override
public void run ( ) {
2021-04-20 23:40:33 +04:00
if ( Build . VERSION . SDK_INT > = 19 ) {
int flags = View . SYSTEM_UI_FLAG_FULLSCREEN |
2021-04-18 07:35:25 +04:00
View . SYSTEM_UI_FLAG_HIDE_NAVIGATION |
View . SYSTEM_UI_FLAG_IMMERSIVE_STICKY |
View . SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
View . SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
View . SYSTEM_UI_FLAG_LAYOUT_STABLE | View . INVISIBLE ;
2021-04-20 23:40:33 +04:00
SDLActivity . this . getWindow ( ) . getDecorView ( ) . setSystemUiVisibility ( flags ) ;
}
2021-04-18 07:35:25 +04:00
}
} ;
public void onSystemUiVisibilityChange ( int visibility ) {
if ( SDLActivity . mFullscreenModeActive & & ( ( visibility & View . SYSTEM_UI_FLAG_FULLSCREEN ) = = 0 | | ( visibility & View . SYSTEM_UI_FLAG_HIDE_NAVIGATION ) = = 0 ) ) {
Handler handler = getWindow ( ) . getDecorView ( ) . getHandler ( ) ;
if ( handler ! = null ) {
handler . removeCallbacks ( rehideSystemUi ) ; // Prevent a hide loop.
handler . postDelayed ( rehideSystemUi , 2000 ) ;
}
}
}
/ * *
* This method is called by SDL using JNI .
* /
public static boolean clipboardHasText ( ) {
return mClipboardHandler . clipboardHasText ( ) ;
}
/ * *
* This method is called by SDL using JNI .
* /
public static String clipboardGetText ( ) {
return mClipboardHandler . clipboardGetText ( ) ;
}
/ * *
* This method is called by SDL using JNI .
* /
public static void clipboardSetText ( String string ) {
mClipboardHandler . clipboardSetText ( string ) ;
}
/ * *
* This method is called by SDL using JNI .
* /
public static int createCustomCursor ( int [ ] colors , int width , int height , int hotSpotX , int hotSpotY ) {
Bitmap bitmap = Bitmap . createBitmap ( colors , width , height , Bitmap . Config . ARGB_8888 ) ;
+ + mLastCursorID ;
if ( Build . VERSION . SDK_INT > = 24 ) {
try {
mCursors . put ( mLastCursorID , PointerIcon . create ( bitmap , hotSpotX , hotSpotY ) ) ;
} catch ( Exception e ) {
return 0 ;
}
} else {
return 0 ;
}
return mLastCursorID ;
}
2022-07-15 06:00:50 +04:00
/ * *
* This method is called by SDL using JNI .
* /
public static void destroyCustomCursor ( int cursorID ) {
if ( Build . VERSION . SDK_INT > = 24 ) {
try {
mCursors . remove ( cursorID ) ;
} catch ( Exception e ) {
}
}
return ;
}
2021-04-18 07:35:25 +04:00
/ * *
* This method is called by SDL using JNI .
* /
public static boolean setCustomCursor ( int cursorID ) {
if ( Build . VERSION . SDK_INT > = 24 ) {
try {
mSurface . setPointerIcon ( mCursors . get ( cursorID ) ) ;
} catch ( Exception e ) {
return false ;
}
} else {
return false ;
}
return true ;
}
/ * *
* This method is called by SDL using JNI .
* /
public static boolean setSystemCursor ( int cursorID ) {
int cursor_type = 0 ; //PointerIcon.TYPE_NULL;
switch ( cursorID ) {
case SDL_SYSTEM_CURSOR_ARROW :
cursor_type = 1000 ; //PointerIcon.TYPE_ARROW;
break ;
case SDL_SYSTEM_CURSOR_IBEAM :
cursor_type = 1008 ; //PointerIcon.TYPE_TEXT;
break ;
case SDL_SYSTEM_CURSOR_WAIT :
cursor_type = 1004 ; //PointerIcon.TYPE_WAIT;
break ;
case SDL_SYSTEM_CURSOR_CROSSHAIR :
cursor_type = 1007 ; //PointerIcon.TYPE_CROSSHAIR;
break ;
case SDL_SYSTEM_CURSOR_WAITARROW :
cursor_type = 1004 ; //PointerIcon.TYPE_WAIT;
break ;
case SDL_SYSTEM_CURSOR_SIZENWSE :
cursor_type = 1017 ; //PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW;
break ;
case SDL_SYSTEM_CURSOR_SIZENESW :
cursor_type = 1016 ; //PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW;
break ;
case SDL_SYSTEM_CURSOR_SIZEWE :
cursor_type = 1014 ; //PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
break ;
case SDL_SYSTEM_CURSOR_SIZENS :
cursor_type = 1015 ; //PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
break ;
case SDL_SYSTEM_CURSOR_SIZEALL :
cursor_type = 1020 ; //PointerIcon.TYPE_GRAB;
break ;
case SDL_SYSTEM_CURSOR_NO :
cursor_type = 1012 ; //PointerIcon.TYPE_NO_DROP;
break ;
case SDL_SYSTEM_CURSOR_HAND :
cursor_type = 1002 ; //PointerIcon.TYPE_HAND;
break ;
}
if ( Build . VERSION . SDK_INT > = 24 ) {
try {
mSurface . setPointerIcon ( PointerIcon . getSystemIcon ( SDL . getContext ( ) , cursor_type ) ) ;
} catch ( Exception e ) {
return false ;
}
}
return true ;
}
/ * *
* This method is called by SDL using JNI .
* /
public static void requestPermission ( String permission , int requestCode ) {
if ( Build . VERSION . SDK_INT < 23 ) {
nativePermissionResult ( requestCode , true ) ;
return ;
}
Activity activity = ( Activity ) getContext ( ) ;
if ( activity . checkSelfPermission ( permission ) ! = PackageManager . PERMISSION_GRANTED ) {
activity . requestPermissions ( new String [ ] { permission } , requestCode ) ;
} else {
nativePermissionResult ( requestCode , true ) ;
}
}
@Override
public void onRequestPermissionsResult ( int requestCode , String [ ] permissions , int [ ] grantResults ) {
2021-04-20 23:40:33 +04:00
boolean result = ( grantResults . length > 0 & & grantResults [ 0 ] = = PackageManager . PERMISSION_GRANTED ) ;
nativePermissionResult ( requestCode , result ) ;
}
/ * *
* This method is called by SDL using JNI .
* /
public static int openURL ( String url )
{
try {
Intent i = new Intent ( Intent . ACTION_VIEW ) ;
i . setData ( Uri . parse ( url ) ) ;
int flags = Intent . FLAG_ACTIVITY_NO_HISTORY | Intent . FLAG_ACTIVITY_MULTIPLE_TASK ;
if ( Build . VERSION . SDK_INT > = 21 ) {
flags | = Intent . FLAG_ACTIVITY_NEW_DOCUMENT ;
} else {
flags | = Intent . FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET ;
}
i . addFlags ( flags ) ;
mSingleton . startActivity ( i ) ;
} catch ( Exception ex ) {
return - 1 ;
2021-04-18 07:35:25 +04:00
}
2021-04-20 23:40:33 +04:00
return 0 ;
2021-04-18 07:35:25 +04:00
}
2021-05-09 13:30:38 +04:00
/ * *
* This method is called by SDL using JNI .
* /
public static int showToast ( String message , int duration , int gravity , int xOffset , int yOffset )
{
if ( null = = mSingleton ) {
return - 1 ;
}
try
{
class OneShotTask implements Runnable {
String mMessage ;
int mDuration ;
int mGravity ;
int mXOffset ;
int mYOffset ;
OneShotTask ( String message , int duration , int gravity , int xOffset , int yOffset ) {
mMessage = message ;
mDuration = duration ;
mGravity = gravity ;
mXOffset = xOffset ;
mYOffset = yOffset ;
}
public void run ( ) {
try
{
Toast toast = Toast . makeText ( mSingleton , mMessage , mDuration ) ;
if ( mGravity > = 0 ) {
toast . setGravity ( mGravity , mXOffset , mYOffset ) ;
}
toast . show ( ) ;
} catch ( Exception ex ) {
Log . e ( TAG , ex . getMessage ( ) ) ;
}
}
}
mSingleton . runOnUiThread ( new OneShotTask ( message , duration , gravity , xOffset , yOffset ) ) ;
} catch ( Exception ex ) {
return - 1 ;
}
return 0 ;
}
2021-04-18 07:35:25 +04:00
}
/ * *
Simple runnable to start the SDL application
* /
class SDLMain implements Runnable {
@Override
public void run ( ) {
// Runs SDL_main()
String library = SDLActivity . mSingleton . getMainSharedObject ( ) ;
String function = SDLActivity . mSingleton . getMainFunction ( ) ;
String [ ] arguments = SDLActivity . mSingleton . getArguments ( ) ;
try {
android . os . Process . setThreadPriority ( android . os . Process . THREAD_PRIORITY_DISPLAY ) ;
} catch ( Exception e ) {
Log . v ( " SDL " , " modify thread properties failed " + e . toString ( ) ) ;
}
Log . v ( " SDL " , " Running main function " + function + " from library " + library ) ;
SDLActivity . nativeRunMain ( library , function , arguments ) ;
Log . v ( " SDL " , " Finished main function " ) ;
2021-04-20 23:40:33 +04:00
if ( SDLActivity . mSingleton ! = null & & ! SDLActivity . mSingleton . isFinishing ( ) ) {
2021-04-18 07:35:25 +04:00
// Let's finish the Activity
SDLActivity . mSDLThread = null ;
SDLActivity . mSingleton . finish ( ) ;
2021-04-20 23:40:33 +04:00
} // else: Activity is already being destroyed
2021-04-18 07:35:25 +04:00
}
}
/ * *
SDLSurface . This is what we draw on , so we need to know when it ' s created
in order to do anything useful .
Because of this , that ' s where we set up the SDL thread
* /
class SDLSurface extends SurfaceView implements SurfaceHolder . Callback ,
View . OnKeyListener , View . OnTouchListener , SensorEventListener {
// Sensors
protected SensorManager mSensorManager ;
protected Display mDisplay ;
// Keep track of the surface size to normalize touch events
protected float mWidth , mHeight ;
// Is SurfaceView ready for rendering
public boolean mIsSurfaceReady ;
// Startup
public SDLSurface ( Context context ) {
super ( context ) ;
getHolder ( ) . addCallback ( this ) ;
setFocusable ( true ) ;
setFocusableInTouchMode ( true ) ;
requestFocus ( ) ;
setOnKeyListener ( this ) ;
setOnTouchListener ( this ) ;
mDisplay = ( ( WindowManager ) context . getSystemService ( Context . WINDOW_SERVICE ) ) . getDefaultDisplay ( ) ;
mSensorManager = ( SensorManager ) context . getSystemService ( Context . SENSOR_SERVICE ) ;
setOnGenericMotionListener ( SDLActivity . getMotionListener ( ) ) ;
// Some arbitrary defaults to avoid a potential division by zero
mWidth = 1 . 0f ;
mHeight = 1 . 0f ;
mIsSurfaceReady = false ;
}
public void handlePause ( ) {
enableSensor ( Sensor . TYPE_ACCELEROMETER , false ) ;
}
public void handleResume ( ) {
setFocusable ( true ) ;
setFocusableInTouchMode ( true ) ;
requestFocus ( ) ;
setOnKeyListener ( this ) ;
setOnTouchListener ( this ) ;
enableSensor ( Sensor . TYPE_ACCELEROMETER , true ) ;
}
public Surface getNativeSurface ( ) {
return getHolder ( ) . getSurface ( ) ;
}
// Called when we have a valid drawing surface
@Override
public void surfaceCreated ( SurfaceHolder holder ) {
Log . v ( " SDL " , " surfaceCreated() " ) ;
SDLActivity . onNativeSurfaceCreated ( ) ;
}
// Called when we lose the surface
@Override
public void surfaceDestroyed ( SurfaceHolder holder ) {
Log . v ( " SDL " , " surfaceDestroyed() " ) ;
// Transition to pause, if needed
SDLActivity . mNextNativeState = SDLActivity . NativeState . PAUSED ;
SDLActivity . handleNativeState ( ) ;
mIsSurfaceReady = false ;
SDLActivity . onNativeSurfaceDestroyed ( ) ;
}
// Called when the surface is resized
@Override
public void surfaceChanged ( SurfaceHolder holder ,
int format , int width , int height ) {
Log . v ( " SDL " , " surfaceChanged() " ) ;
if ( SDLActivity . mSingleton = = null ) {
return ;
}
mWidth = width ;
mHeight = height ;
int nDeviceWidth = width ;
int nDeviceHeight = height ;
try
{
if ( Build . VERSION . SDK_INT > = 17 ) {
2021-04-20 23:40:33 +04:00
DisplayMetrics realMetrics = new DisplayMetrics ( ) ;
2021-04-18 07:35:25 +04:00
mDisplay . getRealMetrics ( realMetrics ) ;
nDeviceWidth = realMetrics . widthPixels ;
nDeviceHeight = realMetrics . heightPixels ;
}
2021-04-20 23:40:33 +04:00
} catch ( Exception ignored ) {
2021-04-18 07:35:25 +04:00
}
synchronized ( SDLActivity . getContext ( ) ) {
// In case we're waiting on a size change after going fullscreen, send a notification.
SDLActivity . getContext ( ) . notifyAll ( ) ;
}
Log . v ( " SDL " , " Window size: " + width + " x " + height ) ;
Log . v ( " SDL " , " Device size: " + nDeviceWidth + " x " + nDeviceHeight ) ;
2021-05-09 13:30:38 +04:00
SDLActivity . nativeSetScreenResolution ( width , height , nDeviceWidth , nDeviceHeight , mDisplay . getRefreshRate ( ) ) ;
2021-04-18 07:35:25 +04:00
SDLActivity . onNativeResize ( ) ;
// Prevent a screen distortion glitch,
// for instance when the device is in Landscape and a Portrait App is resumed.
boolean skip = false ;
int requestedOrientation = SDLActivity . mSingleton . getRequestedOrientation ( ) ;
2021-04-20 23:40:33 +04:00
if ( requestedOrientation = = ActivityInfo . SCREEN_ORIENTATION_PORTRAIT | | requestedOrientation = = ActivityInfo . SCREEN_ORIENTATION_SENSOR_PORTRAIT ) {
2021-04-18 07:35:25 +04:00
if ( mWidth > mHeight ) {
skip = true ;
}
} else if ( requestedOrientation = = ActivityInfo . SCREEN_ORIENTATION_LANDSCAPE | | requestedOrientation = = ActivityInfo . SCREEN_ORIENTATION_SENSOR_LANDSCAPE ) {
if ( mWidth < mHeight ) {
skip = true ;
}
}
// Special Patch for Square Resolution: Black Berry Passport
if ( skip ) {
double min = Math . min ( mWidth , mHeight ) ;
double max = Math . max ( mWidth , mHeight ) ;
if ( max / min < 1 . 20 ) {
Log . v ( " SDL " , " Don't skip on such aspect-ratio. Could be a square resolution. " ) ;
skip = false ;
}
}
// Don't skip in MultiWindow.
if ( skip ) {
if ( Build . VERSION . SDK_INT > = 24 ) {
if ( SDLActivity . mSingleton . isInMultiWindowMode ( ) ) {
Log . v ( " SDL " , " Don't skip in Multi-Window " ) ;
skip = false ;
}
}
}
if ( skip ) {
Log . v ( " SDL " , " Skip .. Surface is not ready. " ) ;
mIsSurfaceReady = false ;
return ;
}
/* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */
SDLActivity . onNativeSurfaceChanged ( ) ;
/* Surface is ready */
mIsSurfaceReady = true ;
SDLActivity . mNextNativeState = SDLActivity . NativeState . RESUMED ;
SDLActivity . handleNativeState ( ) ;
}
// Key events
@Override
public boolean onKey ( View v , int keyCode , KeyEvent event ) {
int deviceId = event . getDeviceId ( ) ;
int source = event . getSource ( ) ;
2021-04-20 23:40:33 +04:00
if ( source = = InputDevice . SOURCE_UNKNOWN ) {
InputDevice device = InputDevice . getDevice ( deviceId ) ;
if ( device ! = null ) {
source = device . getSources ( ) ;
}
}
// if (event.getAction() == KeyEvent.ACTION_DOWN) {
// Log.v("SDL", "key down: " + keyCode + ", deviceId = " + deviceId + ", source = " + source);
// } else if (event.getAction() == KeyEvent.ACTION_UP) {
// Log.v("SDL", "key up: " + keyCode + ", deviceId = " + deviceId + ", source = " + source);
// }
2021-04-18 07:35:25 +04:00
// Dispatch the different events depending on where they come from
// Some SOURCE_JOYSTICK, SOURCE_DPAD or SOURCE_GAMEPAD are also SOURCE_KEYBOARD
// So, we try to process them as JOYSTICK/DPAD/GAMEPAD events first, if that fails we try them as KEYBOARD
//
// Furthermore, it's possible a game controller has SOURCE_KEYBOARD and
// SOURCE_JOYSTICK, while its key events arrive from the keyboard source
// So, retrieve the device itself and check all of its sources
if ( SDLControllerManager . isDeviceSDLJoystick ( deviceId ) ) {
// Note that we process events with specific key codes here
if ( event . getAction ( ) = = KeyEvent . ACTION_DOWN ) {
if ( SDLControllerManager . onNativePadDown ( deviceId , keyCode ) = = 0 ) {
return true ;
}
} else if ( event . getAction ( ) = = KeyEvent . ACTION_UP ) {
if ( SDLControllerManager . onNativePadUp ( deviceId , keyCode ) = = 0 ) {
return true ;
}
}
}
2022-07-15 06:00:50 +04:00
if ( ( source & InputDevice . SOURCE_KEYBOARD ) = = InputDevice . SOURCE_KEYBOARD ) {
2021-04-18 07:35:25 +04:00
if ( event . getAction ( ) = = KeyEvent . ACTION_DOWN ) {
if ( SDLActivity . isTextInputEvent ( event ) ) {
SDLInputConnection . nativeCommitText ( String . valueOf ( ( char ) event . getUnicodeChar ( ) ) , 1 ) ;
}
SDLActivity . onNativeKeyDown ( keyCode ) ;
return true ;
2021-04-20 23:40:33 +04:00
} else if ( event . getAction ( ) = = KeyEvent . ACTION_UP ) {
2021-04-18 07:35:25 +04:00
SDLActivity . onNativeKeyUp ( keyCode ) ;
return true ;
}
}
2022-07-15 06:00:50 +04:00
if ( ( source & InputDevice . SOURCE_MOUSE ) = = InputDevice . SOURCE_MOUSE ) {
2021-04-18 07:35:25 +04:00
// on some devices key events are sent for mouse BUTTON_BACK/FORWARD presses
// they are ignored here because sending them as mouse input to SDL is messy
if ( ( keyCode = = KeyEvent . KEYCODE_BACK ) | | ( keyCode = = KeyEvent . KEYCODE_FORWARD ) ) {
switch ( event . getAction ( ) ) {
case KeyEvent . ACTION_DOWN :
case KeyEvent . ACTION_UP :
// mark the event as handled or it will be handled by system
// handling KEYCODE_BACK by system will call onBackPressed()
return true ;
}
}
}
return false ;
}
// Touch events
@Override
public boolean onTouch ( View v , MotionEvent event ) {
/* Ref: http://developer.android.com/training/gestures/multi.html */
2021-04-20 23:40:33 +04:00
int touchDevId = event . getDeviceId ( ) ;
2021-04-18 07:35:25 +04:00
final int pointerCount = event . getPointerCount ( ) ;
int action = event . getActionMasked ( ) ;
int pointerFingerId ;
int i = - 1 ;
float x , y , p ;
2021-04-20 23:40:33 +04:00
/ *
* Prevent id to be - 1 , since it ' s used in SDL internal for synthetic events
* Appears when using Android emulator , eg :
* adb shell input mouse tap 100 100
* adb shell input touchscreen tap 100 100
* /
if ( touchDevId < 0 ) {
touchDevId - = 1 ;
}
2021-04-18 07:35:25 +04:00
// 12290 = Samsung DeX mode desktop mouse
// 12290 = 0x3002 = 0x2002 | 0x1002 = SOURCE_MOUSE | SOURCE_TOUCHSCREEN
// 0x2 = SOURCE_CLASS_POINTER
if ( event . getSource ( ) = = InputDevice . SOURCE_MOUSE | | event . getSource ( ) = = ( InputDevice . SOURCE_MOUSE | InputDevice . SOURCE_TOUCHSCREEN ) ) {
2021-04-20 23:40:33 +04:00
int mouseButton = 1 ;
2021-04-18 07:35:25 +04:00
try {
2021-04-20 23:40:33 +04:00
Object object = event . getClass ( ) . getMethod ( " getButtonState " ) . invoke ( event ) ;
if ( object ! = null ) {
mouseButton = ( Integer ) object ;
}
} catch ( Exception ignored ) {
2021-04-18 07:35:25 +04:00
}
// We need to check if we're in relative mouse mode and get the axis offset rather than the x/y values
// if we are. We'll leverage our existing mouse motion listener
SDLGenericMotionListener_API12 motionListener = SDLActivity . getMotionListener ( ) ;
x = motionListener . getEventX ( event ) ;
y = motionListener . getEventY ( event ) ;
SDLActivity . onNativeMouse ( mouseButton , action , x , y , motionListener . inRelativeMode ( ) ) ;
} else {
switch ( action ) {
case MotionEvent . ACTION_MOVE :
for ( i = 0 ; i < pointerCount ; i + + ) {
pointerFingerId = event . getPointerId ( i ) ;
x = event . getX ( i ) / mWidth ;
y = event . getY ( i ) / mHeight ;
p = event . getPressure ( i ) ;
if ( p > 1 . 0f ) {
// may be larger than 1.0f on some devices
// see the documentation of getPressure(i)
p = 1 . 0f ;
}
SDLActivity . onNativeTouch ( touchDevId , pointerFingerId , action , x , y , p ) ;
}
break ;
case MotionEvent . ACTION_UP :
case MotionEvent . ACTION_DOWN :
// Primary pointer up/down, the index is always zero
i = 0 ;
2021-04-20 23:40:33 +04:00
/* fallthrough */
2021-04-18 07:35:25 +04:00
case MotionEvent . ACTION_POINTER_UP :
case MotionEvent . ACTION_POINTER_DOWN :
// Non primary pointer up/down
if ( i = = - 1 ) {
i = event . getActionIndex ( ) ;
}
pointerFingerId = event . getPointerId ( i ) ;
x = event . getX ( i ) / mWidth ;
y = event . getY ( i ) / mHeight ;
p = event . getPressure ( i ) ;
if ( p > 1 . 0f ) {
// may be larger than 1.0f on some devices
// see the documentation of getPressure(i)
p = 1 . 0f ;
}
SDLActivity . onNativeTouch ( touchDevId , pointerFingerId , action , x , y , p ) ;
break ;
case MotionEvent . ACTION_CANCEL :
for ( i = 0 ; i < pointerCount ; i + + ) {
pointerFingerId = event . getPointerId ( i ) ;
x = event . getX ( i ) / mWidth ;
y = event . getY ( i ) / mHeight ;
p = event . getPressure ( i ) ;
if ( p > 1 . 0f ) {
// may be larger than 1.0f on some devices
// see the documentation of getPressure(i)
p = 1 . 0f ;
}
SDLActivity . onNativeTouch ( touchDevId , pointerFingerId , MotionEvent . ACTION_UP , x , y , p ) ;
}
break ;
default :
break ;
}
}
return true ;
}
// Sensor events
public void enableSensor ( int sensortype , boolean enabled ) {
// TODO: This uses getDefaultSensor - what if we have >1 accels?
if ( enabled ) {
mSensorManager . registerListener ( this ,
mSensorManager . getDefaultSensor ( sensortype ) ,
SensorManager . SENSOR_DELAY_GAME , null ) ;
} else {
mSensorManager . unregisterListener ( this ,
mSensorManager . getDefaultSensor ( sensortype ) ) ;
}
}
@Override
public void onAccuracyChanged ( Sensor sensor , int accuracy ) {
// TODO
}
@Override
public void onSensorChanged ( SensorEvent event ) {
if ( event . sensor . getType ( ) = = Sensor . TYPE_ACCELEROMETER ) {
// Since we may have an orientation set, we won't receive onConfigurationChanged events.
// We thus should check here.
2021-04-20 23:40:33 +04:00
int newOrientation ;
2021-04-18 07:35:25 +04:00
float x , y ;
switch ( mDisplay . getRotation ( ) ) {
case Surface . ROTATION_90 :
x = - event . values [ 1 ] ;
y = event . values [ 0 ] ;
newOrientation = SDLActivity . SDL_ORIENTATION_LANDSCAPE ;
break ;
case Surface . ROTATION_270 :
x = event . values [ 1 ] ;
y = - event . values [ 0 ] ;
newOrientation = SDLActivity . SDL_ORIENTATION_LANDSCAPE_FLIPPED ;
break ;
case Surface . ROTATION_180 :
x = - event . values [ 0 ] ;
y = - event . values [ 1 ] ;
newOrientation = SDLActivity . SDL_ORIENTATION_PORTRAIT_FLIPPED ;
break ;
2021-04-20 23:40:33 +04:00
case Surface . ROTATION_0 :
2021-04-18 07:35:25 +04:00
default :
x = event . values [ 0 ] ;
y = event . values [ 1 ] ;
newOrientation = SDLActivity . SDL_ORIENTATION_PORTRAIT ;
break ;
}
if ( newOrientation ! = SDLActivity . mCurrentOrientation ) {
SDLActivity . mCurrentOrientation = newOrientation ;
SDLActivity . onNativeOrientationChanged ( newOrientation ) ;
}
SDLActivity . onNativeAccel ( - x / SensorManager . GRAVITY_EARTH ,
y / SensorManager . GRAVITY_EARTH ,
event . values [ 2 ] / SensorManager . GRAVITY_EARTH ) ;
}
}
// Captured pointer events for API 26.
public boolean onCapturedPointerEvent ( MotionEvent event )
{
int action = event . getActionMasked ( ) ;
float x , y ;
switch ( action ) {
case MotionEvent . ACTION_SCROLL :
x = event . getAxisValue ( MotionEvent . AXIS_HSCROLL , 0 ) ;
y = event . getAxisValue ( MotionEvent . AXIS_VSCROLL , 0 ) ;
SDLActivity . onNativeMouse ( 0 , action , x , y , false ) ;
return true ;
case MotionEvent . ACTION_HOVER_MOVE :
case MotionEvent . ACTION_MOVE :
x = event . getX ( 0 ) ;
y = event . getY ( 0 ) ;
SDLActivity . onNativeMouse ( 0 , action , x , y , true ) ;
return true ;
case MotionEvent . ACTION_BUTTON_PRESS :
case MotionEvent . ACTION_BUTTON_RELEASE :
// Change our action value to what SDL's code expects.
if ( action = = MotionEvent . ACTION_BUTTON_PRESS ) {
action = MotionEvent . ACTION_DOWN ;
2021-04-20 23:40:33 +04:00
} else { /* MotionEvent.ACTION_BUTTON_RELEASE */
2021-04-18 07:35:25 +04:00
action = MotionEvent . ACTION_UP ;
}
x = event . getX ( 0 ) ;
y = event . getY ( 0 ) ;
int button = event . getButtonState ( ) ;
SDLActivity . onNativeMouse ( button , action , x , y , true ) ;
return true ;
}
return false ;
}
}
/ * This is a fake invisible editor view that receives the input and defines the
* pan & scan region
* /
class DummyEdit extends View implements View . OnKeyListener {
InputConnection ic ;
public DummyEdit ( Context context ) {
super ( context ) ;
setFocusableInTouchMode ( true ) ;
setFocusable ( true ) ;
setOnKeyListener ( this ) ;
}
@Override
public boolean onCheckIsTextEditor ( ) {
return true ;
}
@Override
public boolean onKey ( View v , int keyCode , KeyEvent event ) {
/ *
* This handles the hardware keyboard input
* /
if ( event . getAction ( ) = = KeyEvent . ACTION_DOWN ) {
if ( SDLActivity . isTextInputEvent ( event ) ) {
ic . commitText ( String . valueOf ( ( char ) event . getUnicodeChar ( ) ) , 1 ) ;
return true ;
}
SDLActivity . onNativeKeyDown ( keyCode ) ;
return true ;
} else if ( event . getAction ( ) = = KeyEvent . ACTION_UP ) {
SDLActivity . onNativeKeyUp ( keyCode ) ;
return true ;
}
return false ;
}
//
@Override
public boolean onKeyPreIme ( int keyCode , KeyEvent event ) {
// As seen on StackOverflow: http://stackoverflow.com/questions/7634346/keyboard-hide-event
// FIXME: Discussion at http://bugzilla.libsdl.org/show_bug.cgi?id=1639
// FIXME: This is not a 100% effective solution to the problem of detecting if the keyboard is showing or not
// FIXME: A more effective solution would be to assume our Layout to be RelativeLayout or LinearLayout
// FIXME: And determine the keyboard presence doing this: http://stackoverflow.com/questions/2150078/how-to-check-visibility-of-software-keyboard-in-android
// FIXME: An even more effective way would be if Android provided this out of the box, but where would the fun be in that :)
if ( event . getAction ( ) = = KeyEvent . ACTION_UP & & keyCode = = KeyEvent . KEYCODE_BACK ) {
if ( SDLActivity . mTextEdit ! = null & & SDLActivity . mTextEdit . getVisibility ( ) = = View . VISIBLE ) {
SDLActivity . onNativeKeyboardFocusLost ( ) ;
}
}
return super . onKeyPreIme ( keyCode , event ) ;
}
@Override
public InputConnection onCreateInputConnection ( EditorInfo outAttrs ) {
ic = new SDLInputConnection ( this , true ) ;
outAttrs . inputType = InputType . TYPE_CLASS_TEXT | InputType . TYPE_TEXT_VARIATION_VISIBLE_PASSWORD ;
outAttrs . imeOptions = EditorInfo . IME_FLAG_NO_EXTRACT_UI
| EditorInfo . IME_FLAG_NO_FULLSCREEN /* API 11 */ ;
return ic ;
}
}
class SDLInputConnection extends BaseInputConnection {
public SDLInputConnection ( View targetView , boolean fullEditor ) {
super ( targetView , fullEditor ) ;
}
@Override
public boolean sendKeyEvent ( KeyEvent event ) {
/ *
* This used to handle the keycodes from soft keyboard ( and IME - translated input from hardkeyboard )
* However , as of Ice Cream Sandwich and later , almost all soft keyboard doesn ' t generate key presses
* and so we need to generate them ourselves in commitText . To avoid duplicates on the handful of keys
* that still do , we empty this out .
* /
/ *
* Return DOES still generate a key event , however . So rather than using it as the ' click a button ' key
* as we do with physical keyboards , let ' s just use it to hide the keyboard .
* /
if ( event . getKeyCode ( ) = = KeyEvent . KEYCODE_ENTER ) {
if ( SDLActivity . onNativeSoftReturnKey ( ) ) {
return true ;
}
}
return super . sendKeyEvent ( event ) ;
}
@Override
public boolean commitText ( CharSequence text , int newCursorPosition ) {
for ( int i = 0 ; i < text . length ( ) ; i + + ) {
char c = text . charAt ( i ) ;
if ( c = = '\n' ) {
if ( SDLActivity . onNativeSoftReturnKey ( ) ) {
return true ;
}
}
nativeGenerateScancodeForUnichar ( c ) ;
}
SDLInputConnection . nativeCommitText ( text . toString ( ) , newCursorPosition ) ;
return super . commitText ( text , newCursorPosition ) ;
}
@Override
public boolean setComposingText ( CharSequence text , int newCursorPosition ) {
nativeSetComposingText ( text . toString ( ) , newCursorPosition ) ;
return super . setComposingText ( text , newCursorPosition ) ;
}
public static native void nativeCommitText ( String text , int newCursorPosition ) ;
public native void nativeGenerateScancodeForUnichar ( char c ) ;
public native void nativeSetComposingText ( String text , int newCursorPosition ) ;
@Override
public boolean deleteSurroundingText ( int beforeLength , int afterLength ) {
// Workaround to capture backspace key. Ref: http://stackoverflow.com/questions/14560344/android-backspace-in-webview-baseinputconnection
// and https://bugzilla.libsdl.org/show_bug.cgi?id=2265
if ( beforeLength > 0 & & afterLength = = 0 ) {
boolean ret = true ;
// backspace(s)
while ( beforeLength - - > 0 ) {
boolean ret_key = sendKeyEvent ( new KeyEvent ( KeyEvent . ACTION_DOWN , KeyEvent . KEYCODE_DEL ) )
& & sendKeyEvent ( new KeyEvent ( KeyEvent . ACTION_UP , KeyEvent . KEYCODE_DEL ) ) ;
ret = ret & & ret_key ;
}
return ret ;
}
return super . deleteSurroundingText ( beforeLength , afterLength ) ;
}
}
2021-04-20 23:40:33 +04:00
class SDLClipboardHandler implements
ClipboardManager . OnPrimaryClipChangedListener {
2021-04-18 07:35:25 +04:00
2021-04-20 23:40:33 +04:00
protected ClipboardManager mClipMgr ;
2021-04-18 07:35:25 +04:00
2021-04-20 23:40:33 +04:00
SDLClipboardHandler ( ) {
mClipMgr = ( ClipboardManager ) SDL . getContext ( ) . getSystemService ( Context . CLIPBOARD_SERVICE ) ;
2021-04-18 07:35:25 +04:00
mClipMgr . addPrimaryClipChangedListener ( this ) ;
}
public boolean clipboardHasText ( ) {
2021-04-20 23:40:33 +04:00
return mClipMgr . hasPrimaryClip ( ) ;
2021-04-18 07:35:25 +04:00
}
public String clipboardGetText ( ) {
2021-04-20 23:40:33 +04:00
ClipData clip = mClipMgr . getPrimaryClip ( ) ;
if ( clip ! = null ) {
ClipData . Item item = clip . getItemAt ( 0 ) ;
if ( item ! = null ) {
CharSequence text = item . getText ( ) ;
if ( text ! = null ) {
return text . toString ( ) ;
}
}
2021-04-18 07:35:25 +04:00
}
return null ;
}
public void clipboardSetText ( String string ) {
mClipMgr . removePrimaryClipChangedListener ( this ) ;
2021-04-20 23:40:33 +04:00
ClipData clip = ClipData . newPlainText ( null , string ) ;
mClipMgr . setPrimaryClip ( clip ) ;
2021-04-18 07:35:25 +04:00
mClipMgr . addPrimaryClipChangedListener ( this ) ;
}
@Override
public void onPrimaryClipChanged ( ) {
SDLActivity . onNativeClipboardChanged ( ) ;
}
}