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