Home | History | Annotate | Download | only in android
      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