android-project/app/src/main/java/org/libsdl/app/SDLActivity.java
author Sam Lantinga <slouken@libsdl.org>
Fri, 02 Nov 2018 17:22:15 -0700
changeset 12388 69af2eac84e9
parent 12348 09d04d83c3c1
child 12491 3b1f484500f0
permissions -rw-r--r--
Fixed bug 4319 - Android remove reflection for PointerIcon

Sylvain

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