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

Sylvain

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