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