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