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