| package org.libsdl.app; |
| |
| import java.util.Hashtable; |
| import java.util.Locale; |
| import java.lang.reflect.Method; |
| import java.lang.Math; |
| |
| import android.app.*; |
| import android.content.*; |
| import android.content.res.Configuration; |
| import android.text.InputType; |
| import android.view.*; |
| import android.view.inputmethod.BaseInputConnection; |
| import android.view.inputmethod.EditorInfo; |
| import android.view.inputmethod.InputConnection; |
| import android.view.inputmethod.InputMethodManager; |
| import android.widget.RelativeLayout; |
| import android.widget.Button; |
| import android.widget.LinearLayout; |
| import android.widget.TextView; |
| import android.os.*; |
| import android.util.DisplayMetrics; |
| import android.util.Log; |
| import android.util.SparseArray; |
| import android.graphics.*; |
| import android.graphics.drawable.Drawable; |
| import android.hardware.*; |
| import android.content.pm.ActivityInfo; |
| import android.content.pm.PackageManager; |
| import android.content.pm.ApplicationInfo; |
| import android.net.Uri; |
| |
| /** |
| SDL Activity |
| */ |
| public class SDLActivity extends Activity implements View.OnSystemUiVisibilityChangeListener { |
| private static final String TAG = "SDL"; |
| |
| public static boolean mIsResumedCalled, mHasFocus; |
| public static final boolean mHasMultiWindow = (Build.VERSION.SDK_INT >= 24); |
| |
| // Cursor types |
| private static final int SDL_SYSTEM_CURSOR_NONE = -1; |
| 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; |
| protected static Locale mCurrentLocale; |
| |
| // 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. */ |
| public static boolean mBrokenLibraries = true; |
| |
| // 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(); |
| } else |
| if (Build.VERSION.SDK_INT >= 24) { |
| 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[] { |
| "hidapi", |
| "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(); |
| mBrokenLibraries = false; /* success */ |
| } 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); |
| |
| mClipboardHandler = new SDLClipboardHandler_API11(); |
| |
| 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); |
| |
| try { |
| if (Build.VERSION.SDK_INT < 24) { |
| mCurrentLocale = getContext().getResources().getConfiguration().locale; |
| } else { |
| mCurrentLocale = getContext().getResources().getConfiguration().getLocales().get(0); |
| } |
| } catch(Exception ignored) { |
| } |
| |
| 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() { |
| final Context context = SDLActivity.getContext(); |
| final Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); |
| |
| int result = SDL_ORIENTATION_UNKNOWN; |
| |
| 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(); |
| } |
| |
| @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(); |
| } |
| } |
| |
| @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. |
| // |
| String trapBack = SDLActivity.nativeGetHint("SDL_ANDROID_TRAP_BACK_BUTTON"); |
| if ((trapBack != null) && trapBack.equals("1")) { |
| // 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 |
| mSurface.handleResume(); |
| } else { |
| nativeResume(); |
| mSurface.handleResume(); |
| } |
| |
| 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_CHANGE_SURFACEVIEW_FORMAT = 4; |
| 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: |
| if (Build.VERSION.SDK_INT < 19) { |
| // This version of Android doesn't support the immersive fullscreen mode |
| break; |
| } |
| if (context instanceof Activity) { |
| Window window = ((Activity) context).getWindow(); |
| if (window != null) { |
| if ((msg.obj instanceof Integer) && (((Integer) msg.obj).intValue() != 0)) { |
| int flags = View.SYSTEM_UI_FLAG_FULLSCREEN | |
| 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; |
| 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; |
| } |
| } |
| } else { |
| Log.e(TAG, "error handling message, getContext() returned no Activity"); |
| } |
| 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) { |
| if ((msg.obj instanceof Integer) && (((Integer) msg.obj).intValue() != 0)) { |
| window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); |
| } else { |
| window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); |
| } |
| } |
| } |
| break; |
| } |
| case COMMAND_CHANGE_SURFACEVIEW_FORMAT: |
| { |
| int format = (Integer) msg.obj; |
| int pf; |
| |
| if (SDLActivity.mSurface == null) { |
| return; |
| } |
| |
| SurfaceHolder holder = SDLActivity.mSurface.getHolder(); |
| if (holder == null) { |
| return; |
| } |
| |
| if (format == 1) { |
| pf = PixelFormat.RGBA_8888; |
| } else if (format == 2) { |
| pf = PixelFormat.RGBX_8888; |
| } else { |
| pf = PixelFormat.RGB_565; |
| } |
| |
| holder.setFormat(pf); |
| |
| 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); |
| |
| if ((Build.VERSION.SDK_INT >= 19) && (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(); |
| android.util.DisplayMetrics realMetrics = new android.util.DisplayMetrics(); |
| display.getRealMetrics( realMetrics ); |
| |
| boolean bFullscreenLayout = ((realMetrics.widthPixels == mSurface.getWidth()) && |
| (realMetrics.heightPixels == mSurface.getHeight())); |
| |
| if (((Integer)data).intValue() == 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; |
| } |
| } |
| |
| 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(); |
| } |
| } |
| } |
| } |
| |
| 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); |
| public static native void nativeSetScreenResolution(int surfaceWidth, int surfaceHeight, int deviceWidth, int deviceHeight, int format, float rate); |
| 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); |
| 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); |
| public static native void onNativeLocaleChanged(); |
| |
| /** |
| * 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; |
| } |
| |
| boolean is_landscape_allowed = (orientation_landscape == -1 ? false : true); |
| boolean is_portrait_allowed = (orientation_portrait == -1 ? false : true); |
| int req = -1; /* Requested orientation */ |
| |
| /* 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); |
| } |
| } |
| } |
| |
| Log.v(TAG, "setOrientation() requestedOrientation=" + req + " width=" + w +" height="+ h +" resizable=" + resizable + " hint=" + hint); |
| 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() |
| { |
| // ChromeOS doesn't provide relative mouse motion via the Android 7 APIs |
| if (isChromebook()) { |
| return false; |
| } |
| |
| // 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; |
| } |
| return mSingleton.sendCommand(command, Integer.valueOf(param)); |
| } |
| |
| /** |
| * 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; |
| } |
| if (Build.MANUFACTURER.equals("Amlogic") && Build.MODEL.startsWith("TV")) { |
| return true; |
| } |
| return false; |
| } |
| |
| public static double getDiagonal() |
| { |
| DisplayMetrics metrics = new DisplayMetrics(); |
| Activity activity = (Activity)getContext(); |
| if (activity == null) { |
| return 0.0; |
| } |
| activity.getWindowManager().getDefaultDisplay().getMetrics(metrics); |
| |
| double dWidthInches = metrics.widthPixels / (double)metrics.xdpi; |
| double dHeightInches = metrics.heightPixels / (double)metrics.ydpi; |
| |
| double dDiagonal = Math.sqrt((dWidthInches * dWidthInches) + (dHeightInches * dHeightInches)); |
| |
| return dDiagonal; |
| } |
| |
| /** |
| * This method is called by SDL using JNI. |
| */ |
| public static boolean isTablet() { |
| // If our diagonal size is seven inches or greater, we consider ourselves a tablet. |
| return (getDiagonal() >= 7.0); |
| } |
| |
| /** |
| * 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(); |
| final Class configClass = config.getClass(); |
| 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 { |
| if (getContext() == null) { |
| return false; |
| } |
| |
| 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) { |
| Log.v(TAG, "exception " + e.toString()); |
| } |
| return false; |
| } |
| |
| // This method is called by SDLControllerManager's API 26 Generic Motion Handler. |
| public static View getContentView() |
| { |
| return mSingleton.mLayout; |
| } |
| |
| 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(); |
| } |
| |
| /** |
| * This method is called by SDL using JNI. |
| */ |
| public static void setSurfaceViewFormat(int format) { |
| mSingleton.sendCommand(COMMAND_CHANGE_SURFACEVIEW_FORMAT, format); |
| return; |
| } |
| |
| // Input |
| |
| /** |
| * This method is called by SDL using JNI. |
| */ |
| public static void initTouch() { |
| int[] ids = InputDevice.getDeviceIds(); |
| |
| for (int i = 0; i < ids.length; ++i) { |
| InputDevice device = InputDevice.getDevice(ids[i]); |
| if (device != null && (device.getSources() & InputDevice.SOURCE_TOUCHSCREEN) != 0) { |
| nativeAddTouch(device.getId(), device.getName()); |
| } |
| } |
| } |
| |
| // Messagebox |
| |
| /** Result of current messagebox. Also used for blocking the calling thread. */ |
| protected final int[] messageboxSelection = new int[1]; |
| |
| /** Id of current dialog. */ |
| protected int dialogs = 0; |
| |
| /** |
| * 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() { |
| showDialog(dialogs++, args); |
| } |
| }); |
| |
| // block the calling thread |
| |
| synchronized (messageboxSelection) { |
| try { |
| messageboxSelection.wait(); |
| } catch (InterruptedException ex) { |
| ex.printStackTrace(); |
| return -1; |
| } |
| } |
| |
| // return selected value |
| |
| return messageboxSelection[0]; |
| } |
| |
| @Override |
| protected Dialog onCreateDialog(int ignore, Bundle args) { |
| |
| // 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 |
| |
| final Dialog dialog = new Dialog(this); |
| 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 |
| |
| dialog.setContentView(content); |
| 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; |
| } |
| }); |
| |
| return dialog; |
| } |
| |
| private final Runnable rehideSystemUi = new Runnable() { |
| @Override |
| public void run() { |
| int flags = View.SYSTEM_UI_FLAG_FULLSCREEN | |
| 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; |
| |
| SDLActivity.this.getWindow().getDecorView().setSystemUiVisibility(flags); |
| } |
| }; |
| |
| 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; |
| } |
| |
| /** |
| * 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) { |
| if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { |
| nativePermissionResult(requestCode, true); |
| } else { |
| nativePermissionResult(requestCode, false); |
| } |
| } |
| |
| /** |
| * 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; |
| } |
| return 0; |
| } |
| } |
| |
| /** |
| 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"); |
| |
| if (SDLActivity.mSingleton == null || SDLActivity.mSingleton.isFinishing()) { |
| // Activity is already being destroyed |
| } else { |
| // Let's finish the Activity |
| SDLActivity.mSDLThread = null; |
| SDLActivity.mSingleton.finish(); |
| } |
| } |
| } |
| |
| |
| /** |
| 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; |
| } |
| |
| int sdlFormat = 0x15151002; // SDL_PIXELFORMAT_RGB565 by default |
| switch (format) { |
| case PixelFormat.RGBA_8888: |
| Log.v("SDL", "pixel format RGBA_8888"); |
| sdlFormat = 0x16462004; // SDL_PIXELFORMAT_RGBA8888 |
| break; |
| case PixelFormat.RGBX_8888: |
| Log.v("SDL", "pixel format RGBX_8888"); |
| sdlFormat = 0x16261804; // SDL_PIXELFORMAT_RGBX8888 |
| break; |
| case PixelFormat.RGB_565: |
| Log.v("SDL", "pixel format RGB_565"); |
| sdlFormat = 0x15151002; // SDL_PIXELFORMAT_RGB565 |
| break; |
| case PixelFormat.RGB_888: |
| Log.v("SDL", "pixel format RGB_888"); |
| // Not sure this is right, maybe SDL_PIXELFORMAT_RGB24 instead? |
| sdlFormat = 0x16161804; // SDL_PIXELFORMAT_RGB888 |
| break; |
| default: |
| Log.v("SDL", "pixel format unknown " + format); |
| break; |
| } |
| |
| mWidth = width; |
| mHeight = height; |
| int nDeviceWidth = width; |
| int nDeviceHeight = height; |
| try |
| { |
| if (Build.VERSION.SDK_INT >= 17) { |
| android.util.DisplayMetrics realMetrics = new android.util.DisplayMetrics(); |
| mDisplay.getRealMetrics( realMetrics ); |
| nDeviceWidth = realMetrics.widthPixels; |
| nDeviceHeight = realMetrics.heightPixels; |
| } |
| } |
| catch ( java.lang.Throwable throwable ) {} |
| |
| 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); |
| SDLActivity.nativeSetScreenResolution(width, height, nDeviceWidth, nDeviceHeight, sdlFormat, mDisplay.getRefreshRate()); |
| 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(); |
| |
| if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) |
| { |
| // Accept any |
| } |
| else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT) |
| { |
| 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(); |
| |
| 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); |
| // } |
| |
| // 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; |
| } |
| } |
| } |
| |
| if ((source & InputDevice.SOURCE_KEYBOARD) != 0) { |
| if (event.getAction() == KeyEvent.ACTION_DOWN) { |
| if (SDLActivity.isTextInputEvent(event)) { |
| SDLInputConnection.nativeCommitText(String.valueOf((char) event.getUnicodeChar()), 1); |
| } |
| SDLActivity.onNativeKeyDown(keyCode); |
| return true; |
| } else if (event.getAction() == KeyEvent.ACTION_UP) { |
| SDLActivity.onNativeKeyUp(keyCode); |
| return true; |
| } |
| } |
| |
| if ((source & InputDevice.SOURCE_MOUSE) != 0) { |
| // 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 */ |
| int touchDevId = event.getDeviceId(); |
| final int pointerCount = event.getPointerCount(); |
| int action = event.getActionMasked(); |
| int pointerFingerId; |
| int mouseButton; |
| int i = -1; |
| float x,y,p; |
| |
| /** |
| * 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; |
| } |
| |
| // 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)) { |
| try { |
| mouseButton = (Integer) event.getClass().getMethod("getButtonState").invoke(event); |
| } catch(Exception e) { |
| mouseButton = 1; // oh well. |
| } |
| |
| // 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; |
| 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. |
| int newOrientation = SDLActivity.SDL_ORIENTATION_UNKNOWN; |
| |
| 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; |
| 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; |
| } |
| else if (action == MotionEvent.ACTION_BUTTON_RELEASE) { |
| 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); |
| } |
| } |
| |
| interface SDLClipboardHandler { |
| |
| public boolean clipboardHasText(); |
| public String clipboardGetText(); |
| public void clipboardSetText(String string); |
| |
| } |
| |
| |
| class SDLClipboardHandler_API11 implements |
| SDLClipboardHandler, |
| android.content.ClipboardManager.OnPrimaryClipChangedListener { |
| |
| protected android.content.ClipboardManager mClipMgr; |
| |
| SDLClipboardHandler_API11() { |
| mClipMgr = (android.content.ClipboardManager) SDL.getContext().getSystemService(Context.CLIPBOARD_SERVICE); |
| mClipMgr.addPrimaryClipChangedListener(this); |
| } |
| |
| @Override |
| public boolean clipboardHasText() { |
| return mClipMgr.hasText(); |
| } |
| |
| @Override |
| public String clipboardGetText() { |
| CharSequence text; |
| text = mClipMgr.getText(); |
| if (text != null) { |
| return text.toString(); |
| } |
| return null; |
| } |
| |
| @Override |
| public void clipboardSetText(String string) { |
| mClipMgr.removePrimaryClipChangedListener(this); |
| mClipMgr.setText(string); |
| mClipMgr.addPrimaryClipChangedListener(this); |
| } |
| |
| @Override |
| public void onPrimaryClipChanged() { |
| SDLActivity.onNativeClipboardChanged(); |
| } |
| |
| } |
| |