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