android-project/app/src/main/java/org/libsdl/app/SDLActivity.java
author Sylvain Becker <sylvain.becker@gmail.com>
Mon, 07 Jan 2019 17:06:50 +0100
changeset 12511 0ef0e4cb7752
parent 12507 6a5107360fbc
child 12513 efddb8c5e161
permissions -rw-r--r--
Android: don't allow multiple instance of SDLActivity

Default launch mode (standard) allows multiple instances of the SDLActivity.
( https://developer.android.com/guide/topics/manifest/activity-element#lmode )

Not sure this is intended in SDL as this doesn't work. There are static
variables in Java, in C code which make this impossible (allow one android_window) and
also Audio print errors.

There is also some code added in onDestroy as if it would be able to
re-initialize: https://hg.libsdl.org/SDL/rev/56e9c709db7e

Bug Android activity life-cycle seems to show there is not transition to get out
of onDestroy()
https://developer.android.com/reference/android/app/Activity#ActivityLifecycle

( can be tested with "adb shell am start my.package.org/.MainActivity"
and "adb shell am start -n my.package.org/.MainActivity" )

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