1 /******************************************************************************* 2 * Copyright 2011 See AUTHORS file. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 ******************************************************************************/ 16 17 package com.badlogic.gdx.backends.android; 18 19 import android.annotation.TargetApi; 20 import android.app.Activity; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.res.Configuration; 24 import android.os.Build; 25 import android.os.Bundle; 26 import android.os.Debug; 27 import android.os.Handler; 28 import android.util.Log; 29 import android.view.Gravity; 30 import android.view.View; 31 import android.view.Window; 32 import android.view.WindowManager; 33 import android.widget.FrameLayout; 34 35 import com.badlogic.gdx.Application; 36 import com.badlogic.gdx.ApplicationListener; 37 import com.badlogic.gdx.Audio; 38 import com.badlogic.gdx.Files; 39 import com.badlogic.gdx.Gdx; 40 import com.badlogic.gdx.Graphics; 41 import com.badlogic.gdx.Input; 42 import com.badlogic.gdx.LifecycleListener; 43 import com.badlogic.gdx.Net; 44 import com.badlogic.gdx.Preferences; 45 import com.badlogic.gdx.backends.android.surfaceview.FillResolutionStrategy; 46 import com.badlogic.gdx.utils.Array; 47 import com.badlogic.gdx.utils.Clipboard; 48 import com.badlogic.gdx.utils.GdxNativesLoader; 49 import com.badlogic.gdx.utils.GdxRuntimeException; 50 import com.badlogic.gdx.utils.SnapshotArray; 51 52 import java.lang.reflect.Method; 53 import java.util.Arrays; 54 55 /** An implementation of the {@link Application} interface for Android. Create an {@link Activity} that derives from this class. In 56 * the {@link Activity#onCreate(Bundle)} method call the {@link #initialize(ApplicationListener)} method specifying the 57 * configuration for the GLSurfaceView. 58 * 59 * @author mzechner */ 60 public class AndroidApplication extends Activity implements AndroidApplicationBase { 61 static { 62 GdxNativesLoader.load(); 63 } 64 65 protected AndroidGraphics graphics; 66 protected AndroidInput input; 67 protected AndroidAudio audio; 68 protected AndroidFiles files; 69 protected AndroidNet net; 70 protected AndroidClipboard clipboard; 71 protected ApplicationListener listener; 72 public Handler handler; 73 protected boolean firstResume = true; 74 protected final Array<Runnable> runnables = new Array<Runnable>(); 75 protected final Array<Runnable> executedRunnables = new Array<Runnable>(); 76 protected final SnapshotArray<LifecycleListener> lifecycleListeners = new SnapshotArray<LifecycleListener>(LifecycleListener.class); 77 private final Array<AndroidEventListener> androidEventListeners = new Array<AndroidEventListener>(); 78 protected int logLevel = LOG_INFO; 79 protected boolean useImmersiveMode = false; 80 protected boolean hideStatusBar = false; 81 private int wasFocusChanged = -1; 82 private boolean isWaitingForAudio = false; 83 84 /** This method has to be called in the {@link Activity#onCreate(Bundle)} method. It sets up all the things necessary to get 85 * input, render via OpenGL and so on. Uses a default {@link AndroidApplicationConfiguration}. 86 * 87 * @param listener the {@link ApplicationListener} implementing the program logic **/ 88 public void initialize (ApplicationListener listener) { 89 AndroidApplicationConfiguration config = new AndroidApplicationConfiguration(); 90 initialize(listener, config); 91 } 92 93 /** This method has to be called in the {@link Activity#onCreate(Bundle)} method. It sets up all the things necessary to get 94 * input, render via OpenGL and so on. You can configure other aspects of the application with the rest of the fields in the 95 * {@link AndroidApplicationConfiguration} instance. 96 * 97 * @param listener the {@link ApplicationListener} implementing the program logic 98 * @param config the {@link AndroidApplicationConfiguration}, defining various settings of the application (use accelerometer, 99 * etc.). */ 100 public void initialize (ApplicationListener listener, AndroidApplicationConfiguration config) { 101 init(listener, config, false); 102 } 103 104 /** This method has to be called in the {@link Activity#onCreate(Bundle)} method. It sets up all the things necessary to get 105 * input, render via OpenGL and so on. Uses a default {@link AndroidApplicationConfiguration}. 106 * <p> 107 * Note: you have to add the returned view to your layout! 108 * 109 * @param listener the {@link ApplicationListener} implementing the program logic 110 * @return the GLSurfaceView of the application */ 111 public View initializeForView (ApplicationListener listener) { 112 AndroidApplicationConfiguration config = new AndroidApplicationConfiguration(); 113 return initializeForView(listener, config); 114 } 115 116 /** This method has to be called in the {@link Activity#onCreate(Bundle)} method. It sets up all the things necessary to get 117 * input, render via OpenGL and so on. You can configure other aspects of the application with the rest of the fields in the 118 * {@link AndroidApplicationConfiguration} instance. 119 * <p> 120 * Note: you have to add the returned view to your layout! 121 * 122 * @param listener the {@link ApplicationListener} implementing the program logic 123 * @param config the {@link AndroidApplicationConfiguration}, defining various settings of the application (use accelerometer, 124 * etc.). 125 * @return the GLSurfaceView of the application */ 126 public View initializeForView (ApplicationListener listener, AndroidApplicationConfiguration config) { 127 init(listener, config, true); 128 return graphics.getView(); 129 } 130 131 private void init (ApplicationListener listener, AndroidApplicationConfiguration config, boolean isForView) { 132 if (this.getVersion() < MINIMUM_SDK) { 133 throw new GdxRuntimeException("LibGDX requires Android API Level " + MINIMUM_SDK + " or later."); 134 } 135 graphics = new AndroidGraphics(this, config, config.resolutionStrategy == null ? new FillResolutionStrategy() 136 : config.resolutionStrategy); 137 input = AndroidInputFactory.newAndroidInput(this, this, graphics.view, config); 138 audio = new AndroidAudio(this, config); 139 this.getFilesDir(); // workaround for Android bug #10515463 140 files = new AndroidFiles(this.getAssets(), this.getFilesDir().getAbsolutePath()); 141 net = new AndroidNet(this); 142 this.listener = listener; 143 this.handler = new Handler(); 144 this.useImmersiveMode = config.useImmersiveMode; 145 this.hideStatusBar = config.hideStatusBar; 146 this.clipboard = new AndroidClipboard(this); 147 148 // Add a specialized audio lifecycle listener 149 addLifecycleListener(new LifecycleListener() { 150 151 @Override 152 public void resume () { 153 // No need to resume audio here 154 } 155 156 @Override 157 public void pause () { 158 audio.pause(); 159 } 160 161 @Override 162 public void dispose () { 163 audio.dispose(); 164 } 165 }); 166 167 Gdx.app = this; 168 Gdx.input = this.getInput(); 169 Gdx.audio = this.getAudio(); 170 Gdx.files = this.getFiles(); 171 Gdx.graphics = this.getGraphics(); 172 Gdx.net = this.getNet(); 173 174 if (!isForView) { 175 try { 176 requestWindowFeature(Window.FEATURE_NO_TITLE); 177 } catch (Exception ex) { 178 log("AndroidApplication", "Content already displayed, cannot request FEATURE_NO_TITLE", ex); 179 } 180 getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); 181 getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN); 182 setContentView(graphics.getView(), createLayoutParams()); 183 } 184 185 createWakeLock(config.useWakelock); 186 hideStatusBar(this.hideStatusBar); 187 useImmersiveMode(this.useImmersiveMode); 188 if (this.useImmersiveMode && getVersion() >= Build.VERSION_CODES.KITKAT) { 189 try { 190 Class<?> vlistener = Class.forName("com.badlogic.gdx.backends.android.AndroidVisibilityListener"); 191 Object o = vlistener.newInstance(); 192 Method method = vlistener.getDeclaredMethod("createListener", AndroidApplicationBase.class); 193 method.invoke(o, this); 194 } catch (Exception e) { 195 log("AndroidApplication", "Failed to create AndroidVisibilityListener", e); 196 } 197 } 198 } 199 200 protected FrameLayout.LayoutParams createLayoutParams () { 201 FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(android.view.ViewGroup.LayoutParams.MATCH_PARENT, 202 android.view.ViewGroup.LayoutParams.MATCH_PARENT); 203 layoutParams.gravity = Gravity.CENTER; 204 return layoutParams; 205 } 206 207 protected void createWakeLock (boolean use) { 208 if (use) { 209 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); 210 } 211 } 212 213 protected void hideStatusBar (boolean hide) { 214 if (!hide || getVersion() < 11) return; 215 216 View rootView = getWindow().getDecorView(); 217 218 try { 219 Method m = View.class.getMethod("setSystemUiVisibility", int.class); 220 if (getVersion() <= 13) m.invoke(rootView, 0x0); 221 m.invoke(rootView, 0x1); 222 } catch (Exception e) { 223 log("AndroidApplication", "Can't hide status bar", e); 224 } 225 } 226 227 @Override 228 public void onWindowFocusChanged (boolean hasFocus) { 229 super.onWindowFocusChanged(hasFocus); 230 useImmersiveMode(this.useImmersiveMode); 231 hideStatusBar(this.hideStatusBar); 232 if (hasFocus) { 233 this.wasFocusChanged = 1; 234 if (this.isWaitingForAudio) { 235 this.audio.resume(); 236 this.isWaitingForAudio = false; 237 } 238 } else { 239 this.wasFocusChanged = 0; 240 } 241 } 242 243 @TargetApi(19) 244 @Override 245 public void useImmersiveMode (boolean use) { 246 if (!use || getVersion() < Build.VERSION_CODES.KITKAT) return; 247 248 View view = getWindow().getDecorView(); 249 try { 250 Method m = View.class.getMethod("setSystemUiVisibility", int.class); 251 int code = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 252 | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN 253 | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; 254 m.invoke(view, code); 255 } catch (Exception e) { 256 log("AndroidApplication", "Can't set immersive mode", e); 257 } 258 } 259 260 @Override 261 protected void onPause () { 262 boolean isContinuous = graphics.isContinuousRendering(); 263 boolean isContinuousEnforced = AndroidGraphics.enforceContinuousRendering; 264 265 // from here we don't want non continuous rendering 266 AndroidGraphics.enforceContinuousRendering = true; 267 graphics.setContinuousRendering(true); 268 // calls to setContinuousRendering(false) from other thread (ex: GLThread) 269 // will be ignored at this point... 270 graphics.pause(); 271 272 input.onPause(); 273 274 if (isFinishing()) { 275 graphics.clearManagedCaches(); 276 graphics.destroy(); 277 } 278 279 AndroidGraphics.enforceContinuousRendering = isContinuousEnforced; 280 graphics.setContinuousRendering(isContinuous); 281 282 graphics.onPauseGLSurfaceView(); 283 284 super.onPause(); 285 } 286 287 @Override 288 protected void onResume () { 289 Gdx.app = this; 290 Gdx.input = this.getInput(); 291 Gdx.audio = this.getAudio(); 292 Gdx.files = this.getFiles(); 293 Gdx.graphics = this.getGraphics(); 294 Gdx.net = this.getNet(); 295 296 input.onResume(); 297 298 if (graphics != null) { 299 graphics.onResumeGLSurfaceView(); 300 } 301 302 if (!firstResume) { 303 graphics.resume(); 304 } else 305 firstResume = false; 306 307 this.isWaitingForAudio = true; 308 if (this.wasFocusChanged == 1 || this.wasFocusChanged == -1) { 309 this.audio.resume(); 310 this.isWaitingForAudio = false; 311 } 312 super.onResume(); 313 } 314 315 @Override 316 protected void onDestroy () { 317 super.onDestroy(); 318 Gdx.app = null; 319 Gdx.input = null; 320 Gdx.audio = null; 321 Gdx.files = null; 322 Gdx.graphics = null; 323 Gdx.net = null; 324 } 325 326 @Override 327 public ApplicationListener getApplicationListener () { 328 return listener; 329 } 330 331 @Override 332 public Audio getAudio () { 333 return audio; 334 } 335 336 @Override 337 public Files getFiles () { 338 return files; 339 } 340 341 @Override 342 public Graphics getGraphics () { 343 return graphics; 344 } 345 346 @Override 347 public AndroidInput getInput () { 348 return input; 349 } 350 351 @Override 352 public Net getNet () { 353 return net; 354 } 355 356 @Override 357 public ApplicationType getType () { 358 return ApplicationType.Android; 359 } 360 361 @Override 362 public int getVersion () { 363 return android.os.Build.VERSION.SDK_INT; 364 } 365 366 @Override 367 public long getJavaHeap () { 368 return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); 369 } 370 371 @Override 372 public long getNativeHeap () { 373 return Debug.getNativeHeapAllocatedSize(); 374 } 375 376 @Override 377 public Preferences getPreferences (String name) { 378 return new AndroidPreferences(getSharedPreferences(name, Context.MODE_PRIVATE)); 379 } 380 381 382 @Override 383 public Clipboard getClipboard () { 384 return clipboard; 385 } 386 387 @Override 388 public void postRunnable (Runnable runnable) { 389 synchronized (runnables) { 390 runnables.add(runnable); 391 Gdx.graphics.requestRendering(); 392 } 393 } 394 395 @Override 396 public void onConfigurationChanged (Configuration config) { 397 super.onConfigurationChanged(config); 398 boolean keyboardAvailable = false; 399 if (config.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO) keyboardAvailable = true; 400 input.keyboardAvailable = keyboardAvailable; 401 } 402 403 @Override 404 public void exit () { 405 handler.post(new Runnable() { 406 @Override 407 public void run () { 408 AndroidApplication.this.finish(); 409 } 410 }); 411 } 412 413 @Override 414 public void debug (String tag, String message) { 415 if (logLevel >= LOG_DEBUG) { 416 Log.d(tag, message); 417 } 418 } 419 420 @Override 421 public void debug (String tag, String message, Throwable exception) { 422 if (logLevel >= LOG_DEBUG) { 423 Log.d(tag, message, exception); 424 } 425 } 426 427 @Override 428 public void log (String tag, String message) { 429 if (logLevel >= LOG_INFO) Log.i(tag, message); 430 } 431 432 @Override 433 public void log (String tag, String message, Throwable exception) { 434 if (logLevel >= LOG_INFO) Log.i(tag, message, exception); 435 } 436 437 @Override 438 public void error (String tag, String message) { 439 if (logLevel >= LOG_ERROR) Log.e(tag, message); 440 } 441 442 @Override 443 public void error (String tag, String message, Throwable exception) { 444 if (logLevel >= LOG_ERROR) Log.e(tag, message, exception); 445 } 446 447 @Override 448 public void setLogLevel (int logLevel) { 449 this.logLevel = logLevel; 450 } 451 452 @Override 453 public int getLogLevel () { 454 return logLevel; 455 } 456 457 @Override 458 public void addLifecycleListener (LifecycleListener listener) { 459 synchronized (lifecycleListeners) { 460 lifecycleListeners.add(listener); 461 } 462 } 463 464 @Override 465 public void removeLifecycleListener (LifecycleListener listener) { 466 synchronized (lifecycleListeners) { 467 lifecycleListeners.removeValue(listener, true); 468 } 469 } 470 471 @Override 472 protected void onActivityResult (int requestCode, int resultCode, Intent data) { 473 super.onActivityResult(requestCode, resultCode, data); 474 475 // forward events to our listeners if there are any installed 476 synchronized (androidEventListeners) { 477 for (int i = 0; i < androidEventListeners.size; i++) { 478 androidEventListeners.get(i).onActivityResult(requestCode, resultCode, data); 479 } 480 } 481 } 482 483 /** Adds an event listener for Android specific event such as onActivityResult(...). */ 484 public void addAndroidEventListener (AndroidEventListener listener) { 485 synchronized (androidEventListeners) { 486 androidEventListeners.add(listener); 487 } 488 } 489 490 /** Removes an event listener for Android specific event such as onActivityResult(...). */ 491 public void removeAndroidEventListener (AndroidEventListener listener) { 492 synchronized (androidEventListeners) { 493 androidEventListeners.removeValue(listener, true); 494 } 495 } 496 497 @Override 498 public Context getContext () { 499 return this; 500 } 501 502 @Override 503 public Array<Runnable> getRunnables () { 504 return runnables; 505 } 506 507 @Override 508 public Array<Runnable> getExecutedRunnables () { 509 return executedRunnables; 510 } 511 512 @Override 513 public SnapshotArray<LifecycleListener> getLifecycleListeners () { 514 return lifecycleListeners; 515 } 516 517 @Override 518 public Window getApplicationWindow () { 519 return this.getWindow(); 520 } 521 522 @Override 523 public Handler getHandler () { 524 return this.handler; 525 } 526 } 527