android-project/app/src/main/java/org/libsdl/app/SDLActivity.java
author Sam Lantinga <slouken@libsdl.org>
Tue, 09 Oct 2018 20:12:40 -0700
changeset 12312 522de4f715bd
parent 12308 4e3b4ddb8787
child 12348 09d04d83c3c1
permissions -rw-r--r--
Removed unneeded variable qualifiers
     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, Object> 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, Object>();
   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         // Ensure we don't return until the resize has actually happened,
   649         // or 250ms have passed.
   650         if (command == COMMAND_CHANGE_WINDOW_STYLE) {
   651             synchronized(SDLActivity.getContext()) {
   652                 try {
   653                     SDLActivity.getContext().wait(250);
   654                 }
   655                 catch (InterruptedException ie) {
   656                     ie.printStackTrace();
   657                 }
   658             }
   659         }
   660 
   661         return result;
   662     }
   663 
   664     // C functions we call
   665     public static native int nativeSetupJNI();
   666     public static native int nativeRunMain(String library, String function, Object arguments);
   667     public static native void nativeLowMemory();
   668     public static native void nativeQuit();
   669     public static native void nativePause();
   670     public static native void nativeResume();
   671     public static native void onNativeDropFile(String filename);
   672     public static native void onNativeResize(int surfaceWidth, int surfaceHeight, int deviceWidth, int deviceHeight, int format, float rate);
   673     public static native void onNativeKeyDown(int keycode);
   674     public static native void onNativeKeyUp(int keycode);
   675     public static native void onNativeKeyboardFocusLost();
   676     public static native void onNativeMouse(int button, int action, float x, float y, boolean relative);
   677     public static native void onNativeTouch(int touchDevId, int pointerFingerId,
   678                                             int action, float x,
   679                                             float y, float p);
   680     public static native void onNativeAccel(float x, float y, float z);
   681     public static native void onNativeClipboardChanged();
   682     public static native void onNativeSurfaceChanged();
   683     public static native void onNativeSurfaceDestroyed();
   684     public static native String nativeGetHint(String name);
   685     public static native void nativeSetenv(String name, String value);
   686     public static native void onNativeOrientationChanged(int orientation);
   687 
   688     /**
   689      * This method is called by SDL using JNI.
   690      */
   691     public static boolean setActivityTitle(String title) {
   692         // Called from SDLMain() thread and can't directly affect the view
   693         return mSingleton.sendCommand(COMMAND_CHANGE_TITLE, title);
   694     }
   695 
   696     /**
   697      * This method is called by SDL using JNI.
   698      */
   699     public static void setWindowStyle(boolean fullscreen) {
   700         // Called from SDLMain() thread and can't directly affect the view
   701         mSingleton.sendCommand(COMMAND_CHANGE_WINDOW_STYLE, fullscreen ? 1 : 0);
   702     }
   703 
   704     /**
   705      * This method is called by SDL using JNI.
   706      * This is a static method for JNI convenience, it calls a non-static method
   707      * so that is can be overridden  
   708      */
   709     public static void setOrientation(int w, int h, boolean resizable, String hint)
   710     {
   711         if (mSingleton != null) {
   712             mSingleton.setOrientationBis(w, h, resizable, hint);
   713         }
   714     }
   715    
   716     /**
   717      * This can be overridden
   718      */
   719     public void setOrientationBis(int w, int h, boolean resizable, String hint) 
   720     {
   721         int orientation = -1;
   722 
   723         if (hint.contains("LandscapeRight") && hint.contains("LandscapeLeft")) {
   724             orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
   725         } else if (hint.contains("LandscapeRight")) {
   726             orientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
   727         } else if (hint.contains("LandscapeLeft")) {
   728             orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
   729         } else if (hint.contains("Portrait") && hint.contains("PortraitUpsideDown")) {
   730             orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT;
   731         } else if (hint.contains("Portrait")) {
   732             orientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
   733         } else if (hint.contains("PortraitUpsideDown")) {
   734             orientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
   735         }
   736 
   737         /* no valid hint */
   738         if (orientation == -1) {
   739             if (resizable) {
   740                 /* no fixed orientation */
   741             } else {
   742                 if (w > h) {
   743                     orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE;
   744                 } else {
   745                     orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT;
   746                 }
   747             }
   748         }
   749 
   750         Log.v("SDL", "setOrientation() orientation=" + orientation + " width=" + w +" height="+ h +" resizable=" + resizable + " hint=" + hint);
   751         if (orientation != -1) {
   752             mSingleton.setRequestedOrientation(orientation);
   753         }
   754     }
   755 
   756     /**
   757      * This method is called by SDL using JNI.
   758      */
   759     public static boolean isScreenKeyboardShown() 
   760     {
   761         if (mTextEdit == null) {
   762             return false;
   763         }
   764 
   765         if (!mScreenKeyboardShown) {
   766             return false;
   767         }
   768 
   769         InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
   770         return imm.isAcceptingText();
   771 
   772     }
   773 
   774     /**
   775      * This method is called by SDL using JNI.
   776      */
   777     public static boolean supportsRelativeMouse()
   778     {
   779         // ChromeOS doesn't provide relative mouse motion via the Android 7 APIs
   780         if (isChromebook()) {
   781             return false;
   782         }
   783 
   784         // DeX mode in Samsung Experience 9.0 and earlier doesn't support relative mice properly under 
   785         // Android 7 APIs, and simply returns no data under Android 8 APIs.
   786         //
   787         // This is fixed in Samsung Experience 9.5, which corresponds to Android 8.1.0, and
   788         // thus SDK version 27.  If we are in DeX mode and not API 27 or higher, as a result,
   789         // we should stick to relative mode.
   790         //
   791         if ((Build.VERSION.SDK_INT < 27) && isDeXMode()) {
   792             return false;
   793         }
   794 
   795         return SDLActivity.getMotionListener().supportsRelativeMouse();
   796     }
   797 
   798     /**
   799      * This method is called by SDL using JNI.
   800      */
   801     public static boolean setRelativeMouseEnabled(boolean enabled)
   802     {
   803         if (enabled && !supportsRelativeMouse()) {
   804             return false;
   805         }
   806 
   807         return SDLActivity.getMotionListener().setRelativeMouseEnabled(enabled);
   808     }
   809 
   810     /**
   811      * This method is called by SDL using JNI.
   812      */
   813     public static boolean sendMessage(int command, int param) {
   814         if (mSingleton == null) {
   815             return false;
   816         }
   817         return mSingleton.sendCommand(command, Integer.valueOf(param));
   818     }
   819 
   820     /**
   821      * This method is called by SDL using JNI.
   822      */
   823     public static Context getContext() {
   824         return SDL.getContext();
   825     }
   826 
   827     /**
   828      * This method is called by SDL using JNI.
   829      */
   830     public static boolean isAndroidTV() {
   831         UiModeManager uiModeManager = (UiModeManager) getContext().getSystemService(UI_MODE_SERVICE);
   832         if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION) {
   833             return true;
   834         }
   835         if (Build.MANUFACTURER.equals("MINIX") && Build.MODEL.equals("NEO-U1")) {
   836             return true;
   837         }
   838         if (Build.MANUFACTURER.equals("Amlogic") && Build.MODEL.equals("X96-W")) {
   839             return true;
   840         }
   841         return false;
   842     }
   843 
   844     /**
   845      * This method is called by SDL using JNI.
   846      */
   847     public static boolean isTablet() {
   848         DisplayMetrics metrics = new DisplayMetrics();
   849         Activity activity = (Activity)getContext();
   850         activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
   851 
   852         double dWidthInches = metrics.widthPixels / (double)metrics.xdpi;
   853         double dHeightInches = metrics.heightPixels / (double)metrics.ydpi;
   854 
   855         double dDiagonal = Math.sqrt((dWidthInches * dWidthInches) + (dHeightInches * dHeightInches));
   856 
   857         // If our diagonal size is seven inches or greater, we consider ourselves a tablet.
   858         return (dDiagonal >= 7.0);
   859     }
   860 
   861     /**
   862      * This method is called by SDL using JNI.
   863      */
   864     public static boolean isChromebook() {
   865         return getContext().getPackageManager().hasSystemFeature("org.chromium.arc.device_management");
   866     }    
   867 
   868     /**
   869      * This method is called by SDL using JNI.
   870      */
   871     public static boolean isDeXMode() {
   872         if (Build.VERSION.SDK_INT < 24) {
   873             return false;
   874         }
   875         try {
   876             final Configuration config = getContext().getResources().getConfiguration();
   877             final Class configClass = config.getClass();
   878             return configClass.getField("SEM_DESKTOP_MODE_ENABLED").getInt(configClass)
   879                     == configClass.getField("semDesktopModeEnabled").getInt(config);
   880         } catch(Exception ignored) {
   881             return false;
   882         }
   883     }
   884 
   885     /**
   886      * This method is called by SDL using JNI.
   887      */
   888     public static DisplayMetrics getDisplayDPI() {
   889         return getContext().getResources().getDisplayMetrics();
   890     }
   891 
   892     /**
   893      * This method is called by SDL using JNI.
   894      */
   895     public static boolean getManifestEnvironmentVariables() {
   896         try {
   897             ApplicationInfo applicationInfo = getContext().getPackageManager().getApplicationInfo(getContext().getPackageName(), PackageManager.GET_META_DATA);
   898             Bundle bundle = applicationInfo.metaData;
   899             if (bundle == null) {
   900                 return false;
   901             }
   902             String prefix = "SDL_ENV.";
   903             final int trimLength = prefix.length();
   904             for (String key : bundle.keySet()) {
   905                 if (key.startsWith(prefix)) {
   906                     String name = key.substring(trimLength);
   907                     String value = bundle.get(key).toString();
   908                     nativeSetenv(name, value);
   909                 }
   910             }
   911             /* environment variables set! */
   912             return true; 
   913         } catch (Exception e) {
   914            Log.v("SDL", "exception " + e.toString());
   915         }
   916         return false;
   917     }
   918 
   919     // This method is called by SDLControllerManager's API 26 Generic Motion Handler.
   920     public static View getContentView() 
   921     {
   922         return mSingleton.mLayout;
   923     }
   924 
   925     static class ShowTextInputTask implements Runnable {
   926         /*
   927          * This is used to regulate the pan&scan method to have some offset from
   928          * the bottom edge of the input region and the top edge of an input
   929          * method (soft keyboard)
   930          */
   931         static final int HEIGHT_PADDING = 15;
   932 
   933         public int x, y, w, h;
   934 
   935         public ShowTextInputTask(int x, int y, int w, int h) {
   936             this.x = x;
   937             this.y = y;
   938             this.w = w;
   939             this.h = h;
   940         }
   941 
   942         @Override
   943         public void run() {
   944             RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(w, h + HEIGHT_PADDING);
   945             params.leftMargin = x;
   946             params.topMargin = y;
   947 
   948             if (mTextEdit == null) {
   949                 mTextEdit = new DummyEdit(SDL.getContext());
   950 
   951                 mLayout.addView(mTextEdit, params);
   952             } else {
   953                 mTextEdit.setLayoutParams(params);
   954             }
   955 
   956             mTextEdit.setVisibility(View.VISIBLE);
   957             mTextEdit.requestFocus();
   958 
   959             InputMethodManager imm = (InputMethodManager) SDL.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
   960             imm.showSoftInput(mTextEdit, 0);
   961 
   962             mScreenKeyboardShown = true;
   963         }
   964     }
   965 
   966     /**
   967      * This method is called by SDL using JNI.
   968      */
   969     public static boolean showTextInput(int x, int y, int w, int h) {
   970         // Transfer the task to the main thread as a Runnable
   971         return mSingleton.commandHandler.post(new ShowTextInputTask(x, y, w, h));
   972     }
   973 
   974     public static boolean isTextInputEvent(KeyEvent event) {
   975       
   976         // Key pressed with Ctrl should be sent as SDL_KEYDOWN/SDL_KEYUP and not SDL_TEXTINPUT
   977         if (Build.VERSION.SDK_INT >= 11) {
   978             if (event.isCtrlPressed()) {
   979                 return false;
   980             }  
   981         }
   982 
   983         return event.isPrintingKey() || event.getKeyCode() == KeyEvent.KEYCODE_SPACE;
   984     }
   985 
   986     /**
   987      * This method is called by SDL using JNI.
   988      */
   989     public static Surface getNativeSurface() {
   990         if (SDLActivity.mSurface == null) {
   991             return null;
   992         }
   993         return SDLActivity.mSurface.getNativeSurface();
   994     }
   995 
   996     // Input
   997 
   998     /**
   999      * This method is called by SDL using JNI.
  1000      * @return an array which may be empty but is never null.
  1001      */
  1002     public static int[] inputGetInputDeviceIds(int sources) {
  1003         int[] ids = InputDevice.getDeviceIds();
  1004         int[] filtered = new int[ids.length];
  1005         int used = 0;
  1006         for (int i = 0; i < ids.length; ++i) {
  1007             InputDevice device = InputDevice.getDevice(ids[i]);
  1008             if ((device != null) && ((device.getSources() & sources) != 0)) {
  1009                 filtered[used++] = device.getId();
  1010             }
  1011         }
  1012         return Arrays.copyOf(filtered, used);
  1013     }
  1014 
  1015     // APK expansion files support
  1016 
  1017     /** com.android.vending.expansion.zipfile.ZipResourceFile object or null. */
  1018     private static Object expansionFile;
  1019 
  1020     /** com.android.vending.expansion.zipfile.ZipResourceFile's getInputStream() or null. */
  1021     private static Method expansionFileMethod;
  1022 
  1023     /**
  1024      * This method is called by SDL using JNI.
  1025      * @return an InputStream on success or null if no expansion file was used.
  1026      * @throws IOException on errors. Message is set for the SDL error message.
  1027      */
  1028     public static InputStream openAPKExpansionInputStream(String fileName) throws IOException {
  1029         // Get a ZipResourceFile representing a merger of both the main and patch files
  1030         if (expansionFile == null) {
  1031             String mainHint = nativeGetHint("SDL_ANDROID_APK_EXPANSION_MAIN_FILE_VERSION");
  1032             if (mainHint == null) {
  1033                 return null; // no expansion use if no main version was set
  1034             }
  1035             String patchHint = nativeGetHint("SDL_ANDROID_APK_EXPANSION_PATCH_FILE_VERSION");
  1036             if (patchHint == null) {
  1037                 return null; // no expansion use if no patch version was set
  1038             }
  1039 
  1040             Integer mainVersion;
  1041             Integer patchVersion;
  1042             try {
  1043                 mainVersion = Integer.valueOf(mainHint);
  1044                 patchVersion = Integer.valueOf(patchHint);
  1045             } catch (NumberFormatException ex) {
  1046                 ex.printStackTrace();
  1047                 throw new IOException("No valid file versions set for APK expansion files", ex);
  1048             }
  1049 
  1050             try {
  1051                 // To avoid direct dependency on Google APK expansion library that is
  1052                 // not a part of Android SDK we access it using reflection
  1053                 expansionFile = Class.forName("com.android.vending.expansion.zipfile.APKExpansionSupport")
  1054                     .getMethod("getAPKExpansionZipFile", Context.class, int.class, int.class)
  1055                     .invoke(null, SDL.getContext(), mainVersion, patchVersion);
  1056 
  1057                 expansionFileMethod = expansionFile.getClass()
  1058                     .getMethod("getInputStream", String.class);
  1059             } catch (Exception ex) {
  1060                 ex.printStackTrace();
  1061                 expansionFile = null;
  1062                 expansionFileMethod = null;
  1063                 throw new IOException("Could not access APK expansion support library", ex);
  1064             }
  1065         }
  1066 
  1067         // Get an input stream for a known file inside the expansion file ZIPs
  1068         InputStream fileStream;
  1069         try {
  1070             fileStream = (InputStream)expansionFileMethod.invoke(expansionFile, fileName);
  1071         } catch (Exception ex) {
  1072             // calling "getInputStream" failed
  1073             ex.printStackTrace();
  1074             throw new IOException("Could not open stream from APK expansion file", ex);
  1075         }
  1076 
  1077         if (fileStream == null) {
  1078             // calling "getInputStream" was successful but null was returned
  1079             throw new IOException("Could not find path in APK expansion file");
  1080         }
  1081 
  1082         return fileStream;
  1083     }
  1084 
  1085     // Messagebox
  1086 
  1087     /** Result of current messagebox. Also used for blocking the calling thread. */
  1088     protected final int[] messageboxSelection = new int[1];
  1089 
  1090     /** Id of current dialog. */
  1091     protected int dialogs = 0;
  1092 
  1093     /**
  1094      * This method is called by SDL using JNI.
  1095      * Shows the messagebox from UI thread and block calling thread.
  1096      * buttonFlags, buttonIds and buttonTexts must have same length.
  1097      * @param buttonFlags array containing flags for every button.
  1098      * @param buttonIds array containing id for every button.
  1099      * @param buttonTexts array containing text for every button.
  1100      * @param colors null for default or array of length 5 containing colors.
  1101      * @return button id or -1.
  1102      */
  1103     public int messageboxShowMessageBox(
  1104             final int flags,
  1105             final String title,
  1106             final String message,
  1107             final int[] buttonFlags,
  1108             final int[] buttonIds,
  1109             final String[] buttonTexts,
  1110             final int[] colors) {
  1111 
  1112         messageboxSelection[0] = -1;
  1113 
  1114         // sanity checks
  1115 
  1116         if ((buttonFlags.length != buttonIds.length) && (buttonIds.length != buttonTexts.length)) {
  1117             return -1; // implementation broken
  1118         }
  1119 
  1120         // collect arguments for Dialog
  1121 
  1122         final Bundle args = new Bundle();
  1123         args.putInt("flags", flags);
  1124         args.putString("title", title);
  1125         args.putString("message", message);
  1126         args.putIntArray("buttonFlags", buttonFlags);
  1127         args.putIntArray("buttonIds", buttonIds);
  1128         args.putStringArray("buttonTexts", buttonTexts);
  1129         args.putIntArray("colors", colors);
  1130 
  1131         // trigger Dialog creation on UI thread
  1132 
  1133         runOnUiThread(new Runnable() {
  1134             @Override
  1135             public void run() {
  1136                 showDialog(dialogs++, args);
  1137             }
  1138         });
  1139 
  1140         // block the calling thread
  1141 
  1142         synchronized (messageboxSelection) {
  1143             try {
  1144                 messageboxSelection.wait();
  1145             } catch (InterruptedException ex) {
  1146                 ex.printStackTrace();
  1147                 return -1;
  1148             }
  1149         }
  1150 
  1151         // return selected value
  1152 
  1153         return messageboxSelection[0];
  1154     }
  1155 
  1156     @Override
  1157     protected Dialog onCreateDialog(int ignore, Bundle args) {
  1158 
  1159         // TODO set values from "flags" to messagebox dialog
  1160 
  1161         // get colors
  1162 
  1163         int[] colors = args.getIntArray("colors");
  1164         int backgroundColor;
  1165         int textColor;
  1166         int buttonBorderColor;
  1167         int buttonBackgroundColor;
  1168         int buttonSelectedColor;
  1169         if (colors != null) {
  1170             int i = -1;
  1171             backgroundColor = colors[++i];
  1172             textColor = colors[++i];
  1173             buttonBorderColor = colors[++i];
  1174             buttonBackgroundColor = colors[++i];
  1175             buttonSelectedColor = colors[++i];
  1176         } else {
  1177             backgroundColor = Color.TRANSPARENT;
  1178             textColor = Color.TRANSPARENT;
  1179             buttonBorderColor = Color.TRANSPARENT;
  1180             buttonBackgroundColor = Color.TRANSPARENT;
  1181             buttonSelectedColor = Color.TRANSPARENT;
  1182         }
  1183 
  1184         // create dialog with title and a listener to wake up calling thread
  1185 
  1186         final Dialog dialog = new Dialog(this);
  1187         dialog.setTitle(args.getString("title"));
  1188         dialog.setCancelable(false);
  1189         dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
  1190             @Override
  1191             public void onDismiss(DialogInterface unused) {
  1192                 synchronized (messageboxSelection) {
  1193                     messageboxSelection.notify();
  1194                 }
  1195             }
  1196         });
  1197 
  1198         // create text
  1199 
  1200         TextView message = new TextView(this);
  1201         message.setGravity(Gravity.CENTER);
  1202         message.setText(args.getString("message"));
  1203         if (textColor != Color.TRANSPARENT) {
  1204             message.setTextColor(textColor);
  1205         }
  1206 
  1207         // create buttons
  1208 
  1209         int[] buttonFlags = args.getIntArray("buttonFlags");
  1210         int[] buttonIds = args.getIntArray("buttonIds");
  1211         String[] buttonTexts = args.getStringArray("buttonTexts");
  1212 
  1213         final SparseArray<Button> mapping = new SparseArray<Button>();
  1214 
  1215         LinearLayout buttons = new LinearLayout(this);
  1216         buttons.setOrientation(LinearLayout.HORIZONTAL);
  1217         buttons.setGravity(Gravity.CENTER);
  1218         for (int i = 0; i < buttonTexts.length; ++i) {
  1219             Button button = new Button(this);
  1220             final int id = buttonIds[i];
  1221             button.setOnClickListener(new View.OnClickListener() {
  1222                 @Override
  1223                 public void onClick(View v) {
  1224                     messageboxSelection[0] = id;
  1225                     dialog.dismiss();
  1226                 }
  1227             });
  1228             if (buttonFlags[i] != 0) {
  1229                 // see SDL_messagebox.h
  1230                 if ((buttonFlags[i] & 0x00000001) != 0) {
  1231                     mapping.put(KeyEvent.KEYCODE_ENTER, button);
  1232                 }
  1233                 if ((buttonFlags[i] & 0x00000002) != 0) {
  1234                     mapping.put(KeyEvent.KEYCODE_ESCAPE, button); /* API 11 */
  1235                 }
  1236             }
  1237             button.setText(buttonTexts[i]);
  1238             if (textColor != Color.TRANSPARENT) {
  1239                 button.setTextColor(textColor);
  1240             }
  1241             if (buttonBorderColor != Color.TRANSPARENT) {
  1242                 // TODO set color for border of messagebox button
  1243             }
  1244             if (buttonBackgroundColor != Color.TRANSPARENT) {
  1245                 Drawable drawable = button.getBackground();
  1246                 if (drawable == null) {
  1247                     // setting the color this way removes the style
  1248                     button.setBackgroundColor(buttonBackgroundColor);
  1249                 } else {
  1250                     // setting the color this way keeps the style (gradient, padding, etc.)
  1251                     drawable.setColorFilter(buttonBackgroundColor, PorterDuff.Mode.MULTIPLY);
  1252                 }
  1253             }
  1254             if (buttonSelectedColor != Color.TRANSPARENT) {
  1255                 // TODO set color for selected messagebox button
  1256             }
  1257             buttons.addView(button);
  1258         }
  1259 
  1260         // create content
  1261 
  1262         LinearLayout content = new LinearLayout(this);
  1263         content.setOrientation(LinearLayout.VERTICAL);
  1264         content.addView(message);
  1265         content.addView(buttons);
  1266         if (backgroundColor != Color.TRANSPARENT) {
  1267             content.setBackgroundColor(backgroundColor);
  1268         }
  1269 
  1270         // add content to dialog and return
  1271 
  1272         dialog.setContentView(content);
  1273         dialog.setOnKeyListener(new Dialog.OnKeyListener() {
  1274             @Override
  1275             public boolean onKey(DialogInterface d, int keyCode, KeyEvent event) {
  1276                 Button button = mapping.get(keyCode);
  1277                 if (button != null) {
  1278                     if (event.getAction() == KeyEvent.ACTION_UP) {
  1279                         button.performClick();
  1280                     }
  1281                     return true; // also for ignored actions
  1282                 }
  1283                 return false;
  1284             }
  1285         });
  1286 
  1287         return dialog;
  1288     }
  1289 
  1290     private final Runnable rehideSystemUi = new Runnable() {
  1291         @Override
  1292         public void run() {
  1293             int flags = View.SYSTEM_UI_FLAG_FULLSCREEN |
  1294                         View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
  1295                         View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY |
  1296                         View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
  1297                         View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
  1298                         View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.INVISIBLE;
  1299 
  1300             SDLActivity.this.getWindow().getDecorView().setSystemUiVisibility(flags);
  1301         }
  1302     };
  1303 
  1304     public void onSystemUiVisibilityChange(int visibility) {
  1305         if (SDLActivity.mFullscreenModeActive && (visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0 || (visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) {
  1306 
  1307             Handler handler = getWindow().getDecorView().getHandler();
  1308             if (handler != null) {
  1309                 handler.removeCallbacks(rehideSystemUi); // Prevent a hide loop.
  1310                 handler.postDelayed(rehideSystemUi, 2000);
  1311             }
  1312 
  1313         }
  1314     }    
  1315 
  1316     /**
  1317      * This method is called by SDL using JNI.
  1318      */
  1319     public static boolean clipboardHasText() {
  1320         return mClipboardHandler.clipboardHasText();
  1321     }
  1322 
  1323     /**
  1324      * This method is called by SDL using JNI.
  1325      */
  1326     public static String clipboardGetText() {
  1327         return mClipboardHandler.clipboardGetText();
  1328     }
  1329 
  1330     /**
  1331      * This method is called by SDL using JNI.
  1332      */
  1333     public static void clipboardSetText(String string) {
  1334         mClipboardHandler.clipboardSetText(string);
  1335     }
  1336 
  1337     /**
  1338      * This method is called by SDL using JNI.
  1339      */
  1340     public static int createCustomCursor(int[] colors, int width, int height, int hotSpotX, int hotSpotY) {
  1341         Bitmap bitmap = Bitmap.createBitmap(colors, width, height, Bitmap.Config.ARGB_8888);
  1342         ++mLastCursorID;
  1343         // This requires API 24, so use reflection to implement this
  1344         try {
  1345             Class PointerIconClass = Class.forName("android.view.PointerIcon");
  1346             Class[] arg_types = new Class[] { Bitmap.class, float.class, float.class };
  1347             Method create = PointerIconClass.getMethod("create", arg_types);
  1348             mCursors.put(mLastCursorID, create.invoke(null, bitmap, hotSpotX, hotSpotY));
  1349         } catch (Exception e) {
  1350             return 0;
  1351         }
  1352         return mLastCursorID;
  1353     }
  1354 
  1355     /**
  1356      * This method is called by SDL using JNI.
  1357      */
  1358     public static boolean setCustomCursor(int cursorID) {
  1359         // This requires API 24, so use reflection to implement this
  1360         try {
  1361             Class PointerIconClass = Class.forName("android.view.PointerIcon");
  1362             Method setPointerIcon = SDLSurface.class.getMethod("setPointerIcon", PointerIconClass);
  1363             setPointerIcon.invoke(mSurface, mCursors.get(cursorID));
  1364         } catch (Exception e) {
  1365             return false;
  1366         }
  1367         return true;
  1368     }
  1369 
  1370     /**
  1371      * This method is called by SDL using JNI.
  1372      */
  1373     public static boolean setSystemCursor(int cursorID) {
  1374         int cursor_type = 0; //PointerIcon.TYPE_NULL;
  1375         switch (cursorID) {
  1376         case SDL_SYSTEM_CURSOR_ARROW:
  1377             cursor_type = 1000; //PointerIcon.TYPE_ARROW;
  1378             break;
  1379         case SDL_SYSTEM_CURSOR_IBEAM:
  1380             cursor_type = 1008; //PointerIcon.TYPE_TEXT;
  1381             break;
  1382         case SDL_SYSTEM_CURSOR_WAIT:
  1383             cursor_type = 1004; //PointerIcon.TYPE_WAIT;
  1384             break;
  1385         case SDL_SYSTEM_CURSOR_CROSSHAIR:
  1386             cursor_type = 1007; //PointerIcon.TYPE_CROSSHAIR;
  1387             break;
  1388         case SDL_SYSTEM_CURSOR_WAITARROW:
  1389             cursor_type = 1004; //PointerIcon.TYPE_WAIT;
  1390             break;
  1391         case SDL_SYSTEM_CURSOR_SIZENWSE:
  1392             cursor_type = 1017; //PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW;
  1393             break;
  1394         case SDL_SYSTEM_CURSOR_SIZENESW:
  1395             cursor_type = 1016; //PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW;
  1396             break;
  1397         case SDL_SYSTEM_CURSOR_SIZEWE:
  1398             cursor_type = 1014; //PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
  1399             break;
  1400         case SDL_SYSTEM_CURSOR_SIZENS:
  1401             cursor_type = 1015; //PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
  1402             break;
  1403         case SDL_SYSTEM_CURSOR_SIZEALL:
  1404             cursor_type = 1020; //PointerIcon.TYPE_GRAB;
  1405             break;
  1406         case SDL_SYSTEM_CURSOR_NO:
  1407             cursor_type = 1012; //PointerIcon.TYPE_NO_DROP;
  1408             break;
  1409         case SDL_SYSTEM_CURSOR_HAND:
  1410             cursor_type = 1002; //PointerIcon.TYPE_HAND;
  1411             break;
  1412         }
  1413         // This requires API 24, so use reflection to implement this
  1414         try {
  1415             Class PointerIconClass = Class.forName("android.view.PointerIcon");
  1416             Class[] arg_types = new Class[] { Context.class, int.class };
  1417             Method getSystemIcon = PointerIconClass.getMethod("getSystemIcon", arg_types);
  1418             Method setPointerIcon = SDLSurface.class.getMethod("setPointerIcon", PointerIconClass);
  1419             setPointerIcon.invoke(mSurface, getSystemIcon.invoke(null, SDL.getContext(), cursor_type));
  1420         } catch (Exception e) {
  1421             return false;
  1422         }
  1423         return true;
  1424     }
  1425 }
  1426 
  1427 /**
  1428     Simple runnable to start the SDL application
  1429 */
  1430 class SDLMain implements Runnable {
  1431     @Override
  1432     public void run() {
  1433         // Runs SDL_main()
  1434         String library = SDLActivity.mSingleton.getMainSharedObject();
  1435         String function = SDLActivity.mSingleton.getMainFunction();
  1436         String[] arguments = SDLActivity.mSingleton.getArguments();
  1437 
  1438         Log.v("SDL", "Running main function " + function + " from library " + library);
  1439         SDLActivity.nativeRunMain(library, function, arguments);
  1440 
  1441         Log.v("SDL", "Finished main function");
  1442 
  1443         // Native thread has finished, let's finish the Activity
  1444         if (!SDLActivity.mExitCalledFromJava) {
  1445             SDLActivity.handleNativeExit();
  1446         }
  1447     }
  1448 }
  1449 
  1450 
  1451 /**
  1452     SDLSurface. This is what we draw on, so we need to know when it's created
  1453     in order to do anything useful.
  1454 
  1455     Because of this, that's where we set up the SDL thread
  1456 */
  1457 class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
  1458     View.OnKeyListener, View.OnTouchListener, SensorEventListener  {
  1459 
  1460     // Sensors
  1461     protected static SensorManager mSensorManager;
  1462     protected static Display mDisplay;
  1463 
  1464     // Keep track of the surface size to normalize touch events
  1465     protected static float mWidth, mHeight;
  1466 
  1467     // Startup
  1468     public SDLSurface(Context context) {
  1469         super(context);
  1470         getHolder().addCallback(this);
  1471 
  1472         setFocusable(true);
  1473         setFocusableInTouchMode(true);
  1474         requestFocus();
  1475         setOnKeyListener(this);
  1476         setOnTouchListener(this);
  1477 
  1478         mDisplay = ((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
  1479         mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
  1480 
  1481         if (Build.VERSION.SDK_INT >= 12) {
  1482             setOnGenericMotionListener(SDLActivity.getMotionListener());
  1483         }
  1484 
  1485         // Some arbitrary defaults to avoid a potential division by zero
  1486         mWidth = 1.0f;
  1487         mHeight = 1.0f;
  1488     }
  1489 
  1490     public void handlePause() {
  1491         enableSensor(Sensor.TYPE_ACCELEROMETER, false);
  1492     }
  1493 
  1494     public void handleResume() {
  1495         setFocusable(true);
  1496         setFocusableInTouchMode(true);
  1497         requestFocus();
  1498         setOnKeyListener(this);
  1499         setOnTouchListener(this);
  1500         enableSensor(Sensor.TYPE_ACCELEROMETER, true);
  1501     }
  1502 
  1503     public Surface getNativeSurface() {
  1504         return getHolder().getSurface();
  1505     }
  1506 
  1507     // Called when we have a valid drawing surface
  1508     @Override
  1509     public void surfaceCreated(SurfaceHolder holder) {
  1510         Log.v("SDL", "surfaceCreated()");
  1511         holder.setType(SurfaceHolder.SURFACE_TYPE_GPU);
  1512     }
  1513 
  1514     // Called when we lose the surface
  1515     @Override
  1516     public void surfaceDestroyed(SurfaceHolder holder) {
  1517         Log.v("SDL", "surfaceDestroyed()");
  1518 
  1519         // Transition to pause, if needed
  1520         SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED;
  1521         SDLActivity.handleNativeState();
  1522 
  1523         SDLActivity.mIsSurfaceReady = false;
  1524         SDLActivity.onNativeSurfaceDestroyed();
  1525     }
  1526 
  1527     // Called when the surface is resized
  1528     @Override
  1529     public void surfaceChanged(SurfaceHolder holder,
  1530                                int format, int width, int height) {
  1531         Log.v("SDL", "surfaceChanged()");
  1532 
  1533         if (SDLActivity.mSingleton == null) {
  1534             return;
  1535         }
  1536 
  1537         int sdlFormat = 0x15151002; // SDL_PIXELFORMAT_RGB565 by default
  1538         switch (format) {
  1539         case PixelFormat.A_8:
  1540             Log.v("SDL", "pixel format A_8");
  1541             break;
  1542         case PixelFormat.LA_88:
  1543             Log.v("SDL", "pixel format LA_88");
  1544             break;
  1545         case PixelFormat.L_8:
  1546             Log.v("SDL", "pixel format L_8");
  1547             break;
  1548         case PixelFormat.RGBA_4444:
  1549             Log.v("SDL", "pixel format RGBA_4444");
  1550             sdlFormat = 0x15421002; // SDL_PIXELFORMAT_RGBA4444
  1551             break;
  1552         case PixelFormat.RGBA_5551:
  1553             Log.v("SDL", "pixel format RGBA_5551");
  1554             sdlFormat = 0x15441002; // SDL_PIXELFORMAT_RGBA5551
  1555             break;
  1556         case PixelFormat.RGBA_8888:
  1557             Log.v("SDL", "pixel format RGBA_8888");
  1558             sdlFormat = 0x16462004; // SDL_PIXELFORMAT_RGBA8888
  1559             break;
  1560         case PixelFormat.RGBX_8888:
  1561             Log.v("SDL", "pixel format RGBX_8888");
  1562             sdlFormat = 0x16261804; // SDL_PIXELFORMAT_RGBX8888
  1563             break;
  1564         case PixelFormat.RGB_332:
  1565             Log.v("SDL", "pixel format RGB_332");
  1566             sdlFormat = 0x14110801; // SDL_PIXELFORMAT_RGB332
  1567             break;
  1568         case PixelFormat.RGB_565:
  1569             Log.v("SDL", "pixel format RGB_565");
  1570             sdlFormat = 0x15151002; // SDL_PIXELFORMAT_RGB565
  1571             break;
  1572         case PixelFormat.RGB_888:
  1573             Log.v("SDL", "pixel format RGB_888");
  1574             // Not sure this is right, maybe SDL_PIXELFORMAT_RGB24 instead?
  1575             sdlFormat = 0x16161804; // SDL_PIXELFORMAT_RGB888
  1576             break;
  1577         default:
  1578             Log.v("SDL", "pixel format unknown " + format);
  1579             break;
  1580         }
  1581 
  1582         mWidth = width;
  1583         mHeight = height;
  1584         int nDeviceWidth = width;
  1585         int nDeviceHeight = height;
  1586         try
  1587         {
  1588             if (Build.VERSION.SDK_INT >= 17) {
  1589                 android.util.DisplayMetrics realMetrics = new android.util.DisplayMetrics();
  1590                 mDisplay.getRealMetrics( realMetrics );
  1591                 nDeviceWidth = realMetrics.widthPixels;
  1592                 nDeviceHeight = realMetrics.heightPixels;
  1593             }
  1594         }
  1595         catch ( java.lang.Throwable throwable ) {}
  1596 
  1597         synchronized(SDLActivity.getContext()) {
  1598             SDLActivity.getContext().notifyAll();
  1599         }
  1600 
  1601         Log.v("SDL", "Window size: " + width + "x" + height);
  1602         Log.v("SDL", "Device size: " + nDeviceWidth + "x" + nDeviceHeight);
  1603         SDLActivity.onNativeResize(width, height, nDeviceWidth, nDeviceHeight, sdlFormat, mDisplay.getRefreshRate());
  1604 
  1605         boolean skip = false;
  1606         int requestedOrientation = SDLActivity.mSingleton.getRequestedOrientation();
  1607 
  1608         if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED)
  1609         {
  1610             // Accept any
  1611         }
  1612         else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT)
  1613         {
  1614             if (mWidth > mHeight) {
  1615                skip = true;
  1616             }
  1617         } else if (requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE) {
  1618             if (mWidth < mHeight) {
  1619                skip = true;
  1620             }
  1621         }
  1622 
  1623         // Special Patch for Square Resolution: Black Berry Passport
  1624         if (skip) {
  1625            double min = Math.min(mWidth, mHeight);
  1626            double max = Math.max(mWidth, mHeight);
  1627 
  1628            if (max / min < 1.20) {
  1629               Log.v("SDL", "Don't skip on such aspect-ratio. Could be a square resolution.");
  1630               skip = false;
  1631            }
  1632         }
  1633 
  1634         if (skip) {
  1635            Log.v("SDL", "Skip .. Surface is not ready.");
  1636            SDLActivity.mIsSurfaceReady = false;
  1637            return;
  1638         }
  1639 
  1640         /* Surface is ready */
  1641         SDLActivity.mIsSurfaceReady = true;
  1642 
  1643         /* If the surface has been previously destroyed by onNativeSurfaceDestroyed, recreate it here */
  1644         SDLActivity.onNativeSurfaceChanged();
  1645 
  1646         SDLActivity.handleNativeState();
  1647     }
  1648 
  1649     // Key events
  1650     @Override
  1651     public boolean onKey(View  v, int keyCode, KeyEvent event) {
  1652         // Dispatch the different events depending on where they come from
  1653         // Some SOURCE_JOYSTICK, SOURCE_DPAD or SOURCE_GAMEPAD are also SOURCE_KEYBOARD
  1654         // So, we try to process them as JOYSTICK/DPAD/GAMEPAD events first, if that fails we try them as KEYBOARD
  1655         //
  1656         // Furthermore, it's possible a game controller has SOURCE_KEYBOARD and
  1657         // SOURCE_JOYSTICK, while its key events arrive from the keyboard source
  1658         // So, retrieve the device itself and check all of its sources
  1659         if (SDLControllerManager.isDeviceSDLJoystick(event.getDeviceId())) {
  1660             // Note that we process events with specific key codes here
  1661             if (event.getAction() == KeyEvent.ACTION_DOWN) {
  1662                 if (SDLControllerManager.onNativePadDown(event.getDeviceId(), keyCode) == 0) {
  1663                     return true;
  1664                 }
  1665             } else if (event.getAction() == KeyEvent.ACTION_UP) {
  1666                 if (SDLControllerManager.onNativePadUp(event.getDeviceId(), keyCode) == 0) {
  1667                     return true;
  1668                 }
  1669             }
  1670         }
  1671 
  1672         if ((event.getSource() & InputDevice.SOURCE_KEYBOARD) != 0) {
  1673             if (event.getAction() == KeyEvent.ACTION_DOWN) {
  1674                 //Log.v("SDL", "key down: " + keyCode);
  1675                 if (SDLActivity.isTextInputEvent(event)) {
  1676                     SDLInputConnection.nativeCommitText(String.valueOf((char) event.getUnicodeChar()), 1);
  1677                 }
  1678                 SDLActivity.onNativeKeyDown(keyCode);
  1679                 return true;
  1680             }
  1681             else if (event.getAction() == KeyEvent.ACTION_UP) {
  1682                 //Log.v("SDL", "key up: " + keyCode);
  1683                 SDLActivity.onNativeKeyUp(keyCode);
  1684                 return true;
  1685             }
  1686         }
  1687 
  1688         if ((event.getSource() & InputDevice.SOURCE_MOUSE) != 0) {
  1689             // on some devices key events are sent for mouse BUTTON_BACK/FORWARD presses
  1690             // they are ignored here because sending them as mouse input to SDL is messy
  1691             if ((keyCode == KeyEvent.KEYCODE_BACK) || (keyCode == KeyEvent.KEYCODE_FORWARD)) {
  1692                 switch (event.getAction()) {
  1693                 case KeyEvent.ACTION_DOWN:
  1694                 case KeyEvent.ACTION_UP:
  1695                     // mark the event as handled or it will be handled by system
  1696                     // handling KEYCODE_BACK by system will call onBackPressed()
  1697                     return true;
  1698                 }
  1699             }
  1700         }
  1701 
  1702         return false;
  1703     }
  1704 
  1705     // Touch events
  1706     @Override
  1707     public boolean onTouch(View v, MotionEvent event) {
  1708         /* Ref: http://developer.android.com/training/gestures/multi.html */
  1709         final int touchDevId = event.getDeviceId();
  1710         final int pointerCount = event.getPointerCount();
  1711         int action = event.getActionMasked();
  1712         int pointerFingerId;
  1713         int mouseButton;
  1714         int i = -1;
  1715         float x,y,p;
  1716 
  1717         // !!! FIXME: dump this SDK check after 2.0.4 ships and require API14.
  1718         // 12290 = Samsung DeX mode desktop mouse
  1719         if ((event.getSource() == InputDevice.SOURCE_MOUSE || event.getSource() == 12290) && SDLActivity.mSeparateMouseAndTouch) {
  1720             if (Build.VERSION.SDK_INT < 14) {
  1721                 mouseButton = 1; // all mouse buttons are the left button
  1722             } else {
  1723                 try {
  1724                     mouseButton = (Integer) event.getClass().getMethod("getButtonState").invoke(event);
  1725                 } catch(Exception e) {
  1726                     mouseButton = 1;    // oh well.
  1727                 }
  1728             }
  1729 
  1730             // We need to check if we're in relative mouse mode and get the axis offset rather than the x/y values
  1731             // if we are.  We'll leverage our existing mouse motion listener
  1732             SDLGenericMotionListener_API12 motionListener = SDLActivity.getMotionListener();
  1733             x = motionListener.getEventX(event);
  1734             y = motionListener.getEventY(event);
  1735 
  1736             SDLActivity.onNativeMouse(mouseButton, action, x, y, motionListener.inRelativeMode());
  1737         } else {
  1738             switch(action) {
  1739                 case MotionEvent.ACTION_MOVE:
  1740                     for (i = 0; i < pointerCount; i++) {
  1741                         pointerFingerId = event.getPointerId(i);
  1742                         x = event.getX(i) / mWidth;
  1743                         y = event.getY(i) / mHeight;
  1744                         p = event.getPressure(i);
  1745                         if (p > 1.0f) {
  1746                             // may be larger than 1.0f on some devices
  1747                             // see the documentation of getPressure(i)
  1748                             p = 1.0f;
  1749                         }
  1750                         SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
  1751                     }
  1752                     break;
  1753 
  1754                 case MotionEvent.ACTION_UP:
  1755                 case MotionEvent.ACTION_DOWN:
  1756                     // Primary pointer up/down, the index is always zero
  1757                     i = 0;
  1758                 case MotionEvent.ACTION_POINTER_UP:
  1759                 case MotionEvent.ACTION_POINTER_DOWN:
  1760                     // Non primary pointer up/down
  1761                     if (i == -1) {
  1762                         i = event.getActionIndex();
  1763                     }
  1764 
  1765                     pointerFingerId = event.getPointerId(i);
  1766                     x = event.getX(i) / mWidth;
  1767                     y = event.getY(i) / mHeight;
  1768                     p = event.getPressure(i);
  1769                     if (p > 1.0f) {
  1770                         // may be larger than 1.0f on some devices
  1771                         // see the documentation of getPressure(i)
  1772                         p = 1.0f;
  1773                     }
  1774                     SDLActivity.onNativeTouch(touchDevId, pointerFingerId, action, x, y, p);
  1775                     break;
  1776 
  1777                 case MotionEvent.ACTION_CANCEL:
  1778                     for (i = 0; i < pointerCount; i++) {
  1779                         pointerFingerId = event.getPointerId(i);
  1780                         x = event.getX(i) / mWidth;
  1781                         y = event.getY(i) / mHeight;
  1782                         p = event.getPressure(i);
  1783                         if (p > 1.0f) {
  1784                             // may be larger than 1.0f on some devices
  1785                             // see the documentation of getPressure(i)
  1786                             p = 1.0f;
  1787                         }
  1788                         SDLActivity.onNativeTouch(touchDevId, pointerFingerId, MotionEvent.ACTION_UP, x, y, p);
  1789                     }
  1790                     break;
  1791 
  1792                 default:
  1793                     break;
  1794             }
  1795         }
  1796 
  1797         return true;
  1798    }
  1799 
  1800     // Sensor events
  1801     public void enableSensor(int sensortype, boolean enabled) {
  1802         // TODO: This uses getDefaultSensor - what if we have >1 accels?
  1803         if (enabled) {
  1804             mSensorManager.registerListener(this,
  1805                             mSensorManager.getDefaultSensor(sensortype),
  1806                             SensorManager.SENSOR_DELAY_GAME, null);
  1807         } else {
  1808             mSensorManager.unregisterListener(this,
  1809                             mSensorManager.getDefaultSensor(sensortype));
  1810         }
  1811     }
  1812 
  1813     @Override
  1814     public void onAccuracyChanged(Sensor sensor, int accuracy) {
  1815         // TODO
  1816     }
  1817 
  1818     @Override
  1819     public void onSensorChanged(SensorEvent event) {
  1820         if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
  1821 
  1822             // Since we may have an orientation set, we won't receive onConfigurationChanged events.
  1823             // We thus should check here.
  1824             int newOrientation = SDLActivity.SDL_ORIENTATION_UNKNOWN;
  1825     
  1826             float x, y;
  1827             switch (mDisplay.getRotation()) {
  1828                 case Surface.ROTATION_90:
  1829                     x = -event.values[1];
  1830                     y = event.values[0];
  1831                     newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE;
  1832                     break;
  1833                 case Surface.ROTATION_270:
  1834                     x = event.values[1];
  1835                     y = -event.values[0];
  1836                     newOrientation = SDLActivity.SDL_ORIENTATION_LANDSCAPE_FLIPPED;
  1837                     break;
  1838                 case Surface.ROTATION_180:
  1839                     x = -event.values[1];
  1840                     y = -event.values[0];
  1841                     newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT_FLIPPED;
  1842                     break;
  1843                 default:
  1844                     x = event.values[0];
  1845                     y = event.values[1];
  1846                     newOrientation = SDLActivity.SDL_ORIENTATION_PORTRAIT;
  1847                     break;
  1848             }
  1849 
  1850             if (newOrientation != SDLActivity.mCurrentOrientation) {
  1851                 SDLActivity.mCurrentOrientation = newOrientation;
  1852                 SDLActivity.onNativeOrientationChanged(newOrientation);
  1853             }
  1854 
  1855             SDLActivity.onNativeAccel(-x / SensorManager.GRAVITY_EARTH,
  1856                                       y / SensorManager.GRAVITY_EARTH,
  1857                                       event.values[2] / SensorManager.GRAVITY_EARTH);
  1858 
  1859             
  1860         }
  1861     }
  1862 
  1863     // Captured pointer events for API 26.
  1864     public boolean onCapturedPointerEvent(MotionEvent event)
  1865     {
  1866         int action = event.getActionMasked();
  1867 
  1868         float x, y;
  1869         switch (action) {
  1870             case MotionEvent.ACTION_SCROLL:
  1871                 x = event.getAxisValue(MotionEvent.AXIS_HSCROLL, 0);
  1872                 y = event.getAxisValue(MotionEvent.AXIS_VSCROLL, 0);
  1873                 SDLActivity.onNativeMouse(0, action, x, y, false);
  1874                 return true;
  1875 
  1876             case MotionEvent.ACTION_HOVER_MOVE:
  1877             case MotionEvent.ACTION_MOVE:
  1878                 x = event.getX(0);
  1879                 y = event.getY(0);
  1880                 SDLActivity.onNativeMouse(0, action, x, y, true);
  1881                 return true;
  1882 
  1883             case MotionEvent.ACTION_BUTTON_PRESS:
  1884             case MotionEvent.ACTION_BUTTON_RELEASE:
  1885 
  1886                 // Change our action value to what SDL's code expects.
  1887                 if (action == MotionEvent.ACTION_BUTTON_PRESS) {
  1888                     action = MotionEvent.ACTION_DOWN;
  1889                 }
  1890                 else if (action == MotionEvent.ACTION_BUTTON_RELEASE) {
  1891                     action = MotionEvent.ACTION_UP;
  1892                 }
  1893 
  1894                 x = event.getX(0);
  1895                 y = event.getY(0);
  1896                 int button = event.getButtonState();
  1897 
  1898                 SDLActivity.onNativeMouse(button, action, x, y, true);
  1899                 return true;
  1900         }
  1901 
  1902         return false;
  1903     }
  1904 
  1905 }
  1906 
  1907 /* This is a fake invisible editor view that receives the input and defines the
  1908  * pan&scan region
  1909  */
  1910 class DummyEdit extends View implements View.OnKeyListener {
  1911     InputConnection ic;
  1912 
  1913     public DummyEdit(Context context) {
  1914         super(context);
  1915         setFocusableInTouchMode(true);
  1916         setFocusable(true);
  1917         setOnKeyListener(this);
  1918     }
  1919 
  1920     @Override
  1921     public boolean onCheckIsTextEditor() {
  1922         return true;
  1923     }
  1924 
  1925     @Override
  1926     public boolean onKey(View v, int keyCode, KeyEvent event) {
  1927         /* 
  1928          * This handles the hardware keyboard input
  1929          */
  1930         if (event.getAction() == KeyEvent.ACTION_DOWN) {
  1931             if (SDLActivity.isTextInputEvent(event)) {
  1932                 ic.commitText(String.valueOf((char) event.getUnicodeChar()), 1);
  1933                 return true;
  1934             }
  1935             SDLActivity.onNativeKeyDown(keyCode);
  1936             return true;
  1937         } else if (event.getAction() == KeyEvent.ACTION_UP) {
  1938             SDLActivity.onNativeKeyUp(keyCode);
  1939             return true;
  1940         }
  1941         return false;
  1942     }
  1943 
  1944     //
  1945     @Override
  1946     public boolean onKeyPreIme (int keyCode, KeyEvent event) {
  1947         // As seen on StackOverflow: http://stackoverflow.com/questions/7634346/keyboard-hide-event
  1948         // FIXME: Discussion at http://bugzilla.libsdl.org/show_bug.cgi?id=1639
  1949         // FIXME: This is not a 100% effective solution to the problem of detecting if the keyboard is showing or not
  1950         // FIXME: A more effective solution would be to assume our Layout to be RelativeLayout or LinearLayout
  1951         // FIXME: And determine the keyboard presence doing this: http://stackoverflow.com/questions/2150078/how-to-check-visibility-of-software-keyboard-in-android
  1952         // FIXME: An even more effective way would be if Android provided this out of the box, but where would the fun be in that :)
  1953         if (event.getAction()==KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
  1954             if (SDLActivity.mTextEdit != null && SDLActivity.mTextEdit.getVisibility() == View.VISIBLE) {
  1955                 SDLActivity.onNativeKeyboardFocusLost();
  1956             }
  1957         }
  1958         return super.onKeyPreIme(keyCode, event);
  1959     }
  1960 
  1961     @Override
  1962     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
  1963         ic = new SDLInputConnection(this, true);
  1964 
  1965         outAttrs.inputType = InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD;
  1966         outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_EXTRACT_UI
  1967                 | EditorInfo.IME_FLAG_NO_FULLSCREEN /* API 11 */;
  1968 
  1969         return ic;
  1970     }
  1971 }
  1972 
  1973 class SDLInputConnection extends BaseInputConnection {
  1974 
  1975     public SDLInputConnection(View targetView, boolean fullEditor) {
  1976         super(targetView, fullEditor);
  1977 
  1978     }
  1979 
  1980     @Override
  1981     public boolean sendKeyEvent(KeyEvent event) {
  1982         /*
  1983          * This used to handle the keycodes from soft keyboard (and IME-translated input from hardkeyboard)
  1984          * However, as of Ice Cream Sandwich and later, almost all soft keyboard doesn't generate key presses
  1985          * and so we need to generate them ourselves in commitText.  To avoid duplicates on the handful of keys
  1986          * that still do, we empty this out.
  1987          */
  1988 
  1989         /*
  1990          * Return DOES still generate a key event, however.  So rather than using it as the 'click a button' key
  1991          * as we do with physical keyboards, let's just use it to hide the keyboard.
  1992          */
  1993 
  1994         if (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) {
  1995             String imeHide = SDLActivity.nativeGetHint("SDL_RETURN_KEY_HIDES_IME");
  1996             if ((imeHide != null) && imeHide.equals("1")) {
  1997                 Context c = SDL.getContext();
  1998                 if (c instanceof SDLActivity) {
  1999                     SDLActivity activity = (SDLActivity)c;
  2000                     activity.sendCommand(SDLActivity.COMMAND_TEXTEDIT_HIDE, null);
  2001                     return true;
  2002                 }
  2003             }
  2004         }
  2005 
  2006 
  2007         return super.sendKeyEvent(event);
  2008     }
  2009 
  2010     @Override
  2011     public boolean commitText(CharSequence text, int newCursorPosition) {
  2012 
  2013         for (int i = 0; i < text.length(); i++) {
  2014             char c = text.charAt(i);
  2015             nativeGenerateScancodeForUnichar(c);
  2016         }
  2017 
  2018         SDLInputConnection.nativeCommitText(text.toString(), newCursorPosition);
  2019 
  2020         return super.commitText(text, newCursorPosition);
  2021     }
  2022 
  2023     @Override
  2024     public boolean setComposingText(CharSequence text, int newCursorPosition) {
  2025 
  2026         nativeSetComposingText(text.toString(), newCursorPosition);
  2027 
  2028         return super.setComposingText(text, newCursorPosition);
  2029     }
  2030 
  2031     public static native void nativeCommitText(String text, int newCursorPosition);
  2032 
  2033     public native void nativeGenerateScancodeForUnichar(char c);
  2034 
  2035     public native void nativeSetComposingText(String text, int newCursorPosition);
  2036 
  2037     @Override
  2038     public boolean deleteSurroundingText(int beforeLength, int afterLength) {
  2039         // Workaround to capture backspace key. Ref: http://stackoverflow.com/questions/14560344/android-backspace-in-webview-baseinputconnection
  2040         // and https://bugzilla.libsdl.org/show_bug.cgi?id=2265
  2041         if (beforeLength > 0 && afterLength == 0) {
  2042             boolean ret = true;
  2043             // backspace(s)
  2044             while (beforeLength-- > 0) {
  2045                boolean ret_key = sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))
  2046                               && sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));
  2047                ret = ret && ret_key; 
  2048             }
  2049             return ret;
  2050         }
  2051 
  2052         return super.deleteSurroundingText(beforeLength, afterLength);
  2053     }
  2054 }
  2055 
  2056 interface SDLClipboardHandler {
  2057 
  2058     public boolean clipboardHasText();
  2059     public String clipboardGetText();
  2060     public void clipboardSetText(String string);
  2061 
  2062 }
  2063 
  2064 
  2065 class SDLClipboardHandler_API11 implements
  2066     SDLClipboardHandler, 
  2067     android.content.ClipboardManager.OnPrimaryClipChangedListener {
  2068 
  2069     protected android.content.ClipboardManager mClipMgr;
  2070 
  2071     SDLClipboardHandler_API11() {
  2072        mClipMgr = (android.content.ClipboardManager) SDL.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
  2073        mClipMgr.addPrimaryClipChangedListener(this);
  2074     }
  2075 
  2076     @Override
  2077     public boolean clipboardHasText() {
  2078        return mClipMgr.hasText();
  2079     }
  2080 
  2081     @Override
  2082     public String clipboardGetText() {
  2083         CharSequence text;
  2084         text = mClipMgr.getText();
  2085         if (text != null) {
  2086            return text.toString();
  2087         }
  2088         return null;
  2089     }
  2090 
  2091     @Override
  2092     public void clipboardSetText(String string) {
  2093        mClipMgr.removePrimaryClipChangedListener(this);
  2094        mClipMgr.setText(string);
  2095        mClipMgr.addPrimaryClipChangedListener(this);
  2096     }
  2097     
  2098     @Override
  2099     public void onPrimaryClipChanged() {
  2100         SDLActivity.onNativeClipboardChanged();
  2101     }
  2102 
  2103 }
  2104 
  2105 class SDLClipboardHandler_Old implements
  2106     SDLClipboardHandler {
  2107    
  2108     protected android.text.ClipboardManager mClipMgrOld;
  2109   
  2110     SDLClipboardHandler_Old() {
  2111        mClipMgrOld = (android.text.ClipboardManager) SDL.getContext().getSystemService(Context.CLIPBOARD_SERVICE);
  2112     }
  2113 
  2114     @Override
  2115     public boolean clipboardHasText() {
  2116        return mClipMgrOld.hasText();
  2117     }
  2118 
  2119     @Override
  2120     public String clipboardGetText() {
  2121        CharSequence text;
  2122        text = mClipMgrOld.getText();
  2123        if (text != null) {
  2124           return text.toString();
  2125        }
  2126        return null;
  2127     }
  2128 
  2129     @Override
  2130     public void clipboardSetText(String string) {
  2131        mClipMgrOld.setText(string);
  2132     }
  2133 }
  2134