+ * Note: you have to add the returned view to your layout! + * + * @param listener the {@link ApplicationListener} implementing the program logic + * @return the GLSurfaceView of the application */ + public View initializeForView (ApplicationListener listener) { + AndroidApplicationConfiguration config = new AndroidApplicationConfiguration(); + return initializeForView(listener, config); + } + + /** This method has to be called in the {@link Activity#onCreate(Bundle)} method. It sets up all the things necessary to get + * input, render via OpenGL and so on. You can configure other aspects of the application with the rest of the fields in the + * {@link AndroidApplicationConfiguration} instance. + *
+ * Note: you have to add the returned view to your layout!
+ *
+ * @param listener the {@link ApplicationListener} implementing the program logic
+ * @param config the {@link AndroidApplicationConfiguration}, defining various settings of the application (use accelerometer,
+ * etc.).
+ * @return the GLSurfaceView of the application */
+ public View initializeForView (ApplicationListener listener, AndroidApplicationConfiguration config) {
+ init(listener, config, true);
+ return graphics.getView();
+ }
+
+ private void init (ApplicationListener listener, AndroidApplicationConfiguration config, boolean isForView) {
+ if (this.getVersion() < MINIMUM_SDK) {
+ throw new GdxRuntimeException("libGDX requires Android API Level " + MINIMUM_SDK + " or later.");
+ }
+ config.nativeLoader.load();
+ setApplicationLogger(new AndroidApplicationLogger());
+ graphics = new AndroidGraphics(this, config,
+ config.resolutionStrategy == null ? new FillResolutionStrategy() : config.resolutionStrategy);
+ input = createInput(this, this, graphics.view, config);
+ audio = createAudio(this, config);
+ files = createFiles();
+ net = new AndroidNet(this, config);
+ this.listener = listener;
+ this.handler = new Handler();
+ this.useImmersiveMode = config.useImmersiveMode;
+ this.clipboard = new AndroidClipboard(this);
+ this.renderUnderCutout = config.renderUnderCutout;
+
+ // Add a specialized audio lifecycle listener
+ addLifecycleListener(new LifecycleListener() {
+
+ @Override
+ public void resume () {
+ // No need to resume audio here
+ }
+
+ @Override
+ public void pause () {
+ audio.pause();
+ }
+
+ @Override
+ public void dispose () {
+ audio.dispose();
+ }
+ });
+
+ Gdx.app = this;
+ Gdx.input = this.getInput();
+ Gdx.audio = this.getAudio();
+ Gdx.files = this.getFiles();
+ Gdx.graphics = this.getGraphics();
+ Gdx.net = this.getNet();
+
+ if (!isForView) {
+ try {
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ } catch (Exception ex) {
+ log("AndroidApplication", "Content already displayed, cannot request FEATURE_NO_TITLE", ex);
+ }
+ getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN);
+ setContentView(graphics.getView(), createLayoutParams());
+ }
+
+ createWakeLock(config.useWakelock);
+ useImmersiveMode(this.useImmersiveMode);
+ if (this.useImmersiveMode) {
+ AndroidVisibilityListener vlistener = new AndroidVisibilityListener();
+ vlistener.createListener(this);
+ }
+
+ // detect an already connected bluetooth keyboardAvailable
+ if (getResources().getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS) input.setKeyboardAvailable(true);
+
+ setLayoutInDisplayCutoutMode(this.renderUnderCutout);
+
+ // As per the docs, it might work unreliable < 23 https://developer.android.com/jetpack/androidx/releases/core#1.5.0-alpha02
+ // So, I guess since 23 is pretty rare we can use the old API for the users
+ /*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ keyboardHeightProvider = new AndroidXKeyboardHeightProvider(this);
+ } else {
+ keyboardHeightProvider = new StandardKeyboardHeightProvider(this);
+ }*/
+ }
+
+ protected FrameLayout.LayoutParams createLayoutParams () {
+ FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT);
+ layoutParams.gravity = Gravity.CENTER;
+ return layoutParams;
+ }
+
+ protected void createWakeLock (boolean use) {
+ if (use) {
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.P)
+ private void setLayoutInDisplayCutoutMode (boolean render) {
+ if (render && getVersion() >= Build.VERSION_CODES.P) {
+ WindowManager.LayoutParams lp = getWindow().getAttributes();
+ lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+ }
+ }
+
+ @Override
+ public void onWindowFocusChanged (boolean hasFocus) {
+ super.onWindowFocusChanged(hasFocus);
+ useImmersiveMode(this.useImmersiveMode);
+ if (hasFocus) {
+ this.wasFocusChanged = 1;
+ if (this.isWaitingForAudio) {
+ this.audio.resume();
+ this.isWaitingForAudio = false;
+ }
+ } else {
+ this.wasFocusChanged = 0;
+ }
+ }
+
+ @Override
+ public void useImmersiveMode (boolean use) {
+ if (!use) return;
+
+ View view = getWindow().getDecorView();
+ int code = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
+ view.setSystemUiVisibility(code);
+ }
+
+ @Override
+ protected void onPause () {
+ boolean isContinuous = graphics.isContinuousRendering();
+ boolean isContinuousEnforced = AndroidGraphics.enforceContinuousRendering;
+
+ // from here we don't want non continuous rendering
+ AndroidGraphics.enforceContinuousRendering = true;
+ graphics.setContinuousRendering(true);
+ // calls to setContinuousRendering(false) from other thread (ex: GLThread)
+ // will be ignored at this point...
+ graphics.pause();
+
+ input.onPause();
+
+ if (isFinishing()) {
+ graphics.clearManagedCaches();
+ graphics.destroy();
+ }
+
+ AndroidGraphics.enforceContinuousRendering = isContinuousEnforced;
+ graphics.setContinuousRendering(isContinuous);
+
+ graphics.onPauseGLSurfaceView();
+
+ super.onPause();
+ //keyboardHeightProvider.setKeyboardHeightObserver(null);
+ }
+
+ @Override
+ protected void onResume () {
+ Gdx.app = this;
+ Gdx.input = this.getInput();
+ Gdx.audio = this.getAudio();
+ Gdx.files = this.getFiles();
+ Gdx.graphics = this.getGraphics();
+ Gdx.net = this.getNet();
+
+ input.onResume();
+
+ if (graphics != null) {
+ graphics.onResumeGLSurfaceView();
+ }
+
+ if (!firstResume) {
+ graphics.resume();
+ } else
+ firstResume = false;
+
+ this.isWaitingForAudio = true;
+ if (this.wasFocusChanged == 1 || this.wasFocusChanged == -1) {
+ this.audio.resume();
+ this.isWaitingForAudio = false;
+ }
+ super.onResume();
+ /*keyboardHeightProvider.setKeyboardHeightObserver((DefaultAndroidInput)Gdx.input);
+ ((AndroidGraphics)getGraphics()).getView().post(new Runnable() {
+ @Override
+ public void run () {
+ keyboardHeightProvider.start();
+ }
+ });*/
+ }
+
+ @Override
+ protected void onDestroy () {
+ super.onDestroy();
+ //keyboardHeightProvider.close();
+ }
+
+ @Override
+ public ApplicationListener getApplicationListener () {
+ return listener;
+ }
+
+ @Override
+ public Audio getAudio () {
+ return audio;
+ }
+
+ @Override
+ public AndroidInput getInput () {
+ return input;
+ }
+
+ @Override
+ public Files getFiles () {
+ return files;
+ }
+
+ @Override
+ public Graphics getGraphics () {
+ return graphics;
+ }
+
+ @Override
+ public Net getNet () {
+ return net;
+ }
+
+ @Override
+ public ApplicationType getType () {
+ return ApplicationType.Android;
+ }
+
+ @Override
+ public int getVersion () {
+ return android.os.Build.VERSION.SDK_INT;
+ }
+
+ @Override
+ public long getJavaHeap () {
+ return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
+ }
+
+ @Override
+ public long getNativeHeap () {
+ return Debug.getNativeHeapAllocatedSize();
+ }
+
+ @Override
+ public Preferences getPreferences (String name) {
+ return new AndroidPreferences(getSharedPreferences(name, Context.MODE_PRIVATE));
+ }
+
+ @Override
+ public Clipboard getClipboard () {
+ return clipboard;
+ }
+
+ @Override
+ public void postRunnable (Runnable runnable) {
+ synchronized (runnables) {
+ runnables.add(runnable);
+ Gdx.graphics.requestRendering();
+ }
+ }
+
+ @Override
+ public void onConfigurationChanged (Configuration config) {
+ super.onConfigurationChanged(config);
+ boolean keyboardAvailable = false;
+ if (config.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO) keyboardAvailable = true;
+ input.setKeyboardAvailable(keyboardAvailable);
+ }
+
+ @Override
+ public void exit () {
+ handler.post(new Runnable() {
+ @Override
+ public void run () {
+ ForgeAndroidApplication.this.finish();
+ }
+ });
+ }
+
+ @Override
+ public void debug (String tag, String message) {
+ if (logLevel >= LOG_DEBUG) getApplicationLogger().debug(tag, message);
+ }
+
+ @Override
+ public void debug (String tag, String message, Throwable exception) {
+ if (logLevel >= LOG_DEBUG) getApplicationLogger().debug(tag, message, exception);
+ }
+
+ @Override
+ public void log (String tag, String message) {
+ if (logLevel >= LOG_INFO) getApplicationLogger().log(tag, message);
+ }
+
+ @Override
+ public void log (String tag, String message, Throwable exception) {
+ if (logLevel >= LOG_INFO) getApplicationLogger().log(tag, message, exception);
+ }
+
+ @Override
+ public void error (String tag, String message) {
+ if (logLevel >= LOG_ERROR) getApplicationLogger().error(tag, message);
+ }
+
+ @Override
+ public void error (String tag, String message, Throwable exception) {
+ if (logLevel >= LOG_ERROR) getApplicationLogger().error(tag, message, exception);
+ }
+
+ @Override
+ public void setLogLevel (int logLevel) {
+ this.logLevel = logLevel;
+ }
+
+ @Override
+ public int getLogLevel () {
+ return logLevel;
+ }
+
+ @Override
+ public void setApplicationLogger (ApplicationLogger applicationLogger) {
+ this.applicationLogger = applicationLogger;
+ }
+
+ @Override
+ public ApplicationLogger getApplicationLogger () {
+ return applicationLogger;
+ }
+
+ @Override
+ public void addLifecycleListener (LifecycleListener listener) {
+ synchronized (lifecycleListeners) {
+ lifecycleListeners.add(listener);
+ }
+ }
+
+ @Override
+ public void removeLifecycleListener (LifecycleListener listener) {
+ synchronized (lifecycleListeners) {
+ lifecycleListeners.removeValue(listener, true);
+ }
+ }
+
+ @Override
+ protected void onActivityResult (int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+
+ // forward events to our listeners if there are any installed
+ synchronized (androidEventListeners) {
+ for (int i = 0; i < androidEventListeners.size; i++) {
+ androidEventListeners.get(i).onActivityResult(requestCode, resultCode, data);
+ }
+ }
+ }
+
+ /** Adds an event listener for Android specific event such as onActivityResult(...). */
+ public void addAndroidEventListener (AndroidEventListener listener) {
+ synchronized (androidEventListeners) {
+ androidEventListeners.add(listener);
+ }
+ }
+
+ /** Removes an event listener for Android specific event such as onActivityResult(...). */
+ public void removeAndroidEventListener (AndroidEventListener listener) {
+ synchronized (androidEventListeners) {
+ androidEventListeners.removeValue(listener, true);
+ }
+ }
+
+ @Override
+ public Context getContext () {
+ return this;
+ }
+
+ @Override
+ public Array