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