1 /* 2 * Copyright 2010 Mario Zechner (contact@badlogicgames.com), Nathan Sweet (admin@esotericsoftware.com) 3 * 4 * Modified by Elijah Cornell 5 * 2013.01 Modified by Jaroslaw Wisniewski <j.wisniewski@appsisle.com> 6 * 7 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the 8 * License. You may obtain a copy of the License at 9 * 10 * http://www.apache.org/licenses/LICENSE-2.0 11 * 12 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" 13 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language 14 * governing permissions and limitations under the License. 15 */ 16 17 package com.badlogic.gdx.backends.android; 18 19 import java.lang.reflect.Method; 20 import java.util.Arrays; 21 22 import android.content.Context; 23 import android.content.Intent; 24 import android.os.Debug; 25 import android.os.Handler; 26 import android.os.Looper; 27 import android.util.Log; 28 import android.view.View; 29 import android.view.Window; 30 import android.view.WindowManager; 31 32 import com.badlogic.gdx.Application; 33 import com.badlogic.gdx.ApplicationListener; 34 import com.badlogic.gdx.Audio; 35 import com.badlogic.gdx.Files; 36 import com.badlogic.gdx.Gdx; 37 import com.badlogic.gdx.Graphics; 38 import com.badlogic.gdx.Input; 39 import com.badlogic.gdx.LifecycleListener; 40 import com.badlogic.gdx.Net; 41 import com.badlogic.gdx.Preferences; 42 import com.badlogic.gdx.backends.android.surfaceview.FillResolutionStrategy; 43 import com.badlogic.gdx.utils.Array; 44 import com.badlogic.gdx.utils.Clipboard; 45 import com.badlogic.gdx.utils.GdxNativesLoader; 46 import com.badlogic.gdx.utils.GdxRuntimeException; 47 import com.badlogic.gdx.utils.SnapshotArray; 48 49 /** An implementation of the {@link Application} interface to be used with an AndroidLiveWallpaperService. Not directly 50 * constructable, instead the {@link AndroidLiveWallpaperService} will create this class internally. 51 * 52 * @author mzechner */ 53 public class AndroidLiveWallpaper implements AndroidApplicationBase { 54 static { GdxNativesLoader.load()55 GdxNativesLoader.load(); 56 } 57 58 protected AndroidLiveWallpaperService service; 59 60 protected AndroidGraphicsLiveWallpaper graphics; 61 protected AndroidInput input; 62 protected AndroidAudio audio; 63 protected AndroidFiles files; 64 protected AndroidNet net; 65 protected AndroidClipboard clipboard; 66 protected ApplicationListener listener; 67 protected boolean firstResume = true; 68 protected final Array<Runnable> runnables = new Array<Runnable>(); 69 protected final Array<Runnable> executedRunnables = new Array<Runnable>(); 70 protected final SnapshotArray<LifecycleListener> lifecycleListeners = new SnapshotArray<LifecycleListener>(LifecycleListener.class); 71 protected int logLevel = LOG_INFO; 72 AndroidLiveWallpaper(AndroidLiveWallpaperService service)73 public AndroidLiveWallpaper (AndroidLiveWallpaperService service) { 74 this.service = service; 75 } 76 initialize(ApplicationListener listener, AndroidApplicationConfiguration config)77 public void initialize (ApplicationListener listener, AndroidApplicationConfiguration config) { 78 if (this.getVersion() < MINIMUM_SDK) { 79 throw new GdxRuntimeException("LibGDX requires Android API Level " + MINIMUM_SDK + " or later."); 80 } 81 graphics = new AndroidGraphicsLiveWallpaper(this, config, config.resolutionStrategy == null ? new FillResolutionStrategy() 82 : config.resolutionStrategy); 83 84 // factory in use, but note: AndroidInputFactory causes exceptions when obfuscated: java.lang.RuntimeException: Couldn't 85 // construct AndroidInput, this should never happen, proguard deletes constructor used only by reflection 86 input = AndroidInputFactory.newAndroidInput(this, this.getService(), graphics.view, config); 87 // input = new AndroidInput(this, this.getService(), null, config); 88 89 audio = new AndroidAudio(this.getService(), config); 90 91 // added initialization of android local storage: /data/data/<app package>/files/ 92 this.getService().getFilesDir(); // workaround for Android bug #10515463 93 files = new AndroidFiles(this.getService().getAssets(), this.getService().getFilesDir().getAbsolutePath()); 94 net = new AndroidNet(this); 95 this.listener = listener; 96 clipboard = new AndroidClipboard(this.getService()); 97 98 // Unlike activity, fragment and daydream applications there's no need for a specialized audio listener. 99 // See description in onPause method. 100 101 Gdx.app = this; 102 Gdx.input = input; 103 Gdx.audio = audio; 104 Gdx.files = files; 105 Gdx.graphics = graphics; 106 Gdx.net = net; 107 } 108 onPause()109 public void onPause () { 110 if (AndroidLiveWallpaperService.DEBUG) Log.d(AndroidLiveWallpaperService.TAG, " > AndroidLiveWallpaper - onPause()"); 111 112 // IMPORTANT! 113 // jw: graphics.pause is never called, graphics.pause works on most devices but not on all.. 114 // for example on Samsung Galaxy Tab (GT-P6800) on android 4.0.4 invoking graphics.pause causes "Fatal Signal 11" 115 // near mEglHelper.swap() in GLSurfaceView while processing next onPause event. 116 // See related issue: 117 // http://code.google.com/p/libgdx/issues/detail?id=541 118 // the problem with graphics.pause occurs while using OpenGL 2.0 and original GLSurfaceView while rotating device 119 // in lwp preview 120 // in my opinion it is a bug of android not libgdx, even example Cubic live wallpaper from 121 // Android SDK crashes on affected devices.......... and on some configurations of android emulator too. 122 // 123 // My wallpaper was rejected on Samsung Apps because of this issue, so I decided to disable graphics.pause.. 124 // also I moved audio lifecycle methods from AndroidGraphicsLiveWallpaper into this class 125 126 // graphics.pause(); 127 // if (AndroidLiveWallpaperService.DEBUG) 128 // Log.d(AndroidLiveWallpaperService.TAG, " > AndroidLiveWallpaper - onPause() application paused!"); 129 audio.pause(); 130 131 input.onPause(); 132 133 if (graphics != null) { 134 graphics.onPauseGLSurfaceView(); 135 } 136 137 if (AndroidLiveWallpaperService.DEBUG) Log.d(AndroidLiveWallpaperService.TAG, " > AndroidLiveWallpaper - onPause() done!"); 138 } 139 onResume()140 public void onResume () { 141 Gdx.app = this; 142 Gdx.input = input; 143 Gdx.audio = audio; 144 Gdx.files = files; 145 Gdx.graphics = graphics; 146 Gdx.net = net; 147 148 input.onResume(); 149 150 if (graphics != null) { 151 graphics.onResumeGLSurfaceView(); 152 } 153 154 if (!firstResume) { 155 audio.resume(); 156 graphics.resume(); 157 } else 158 firstResume = false; 159 } 160 onDestroy()161 public void onDestroy () { 162 163 // it is too late to call graphics.destroy - it needs live gl GLThread and gl context, otherwise it will cause of deadlock 164 // if (graphics != null) { 165 // graphics.clearManagedCaches(); 166 // graphics.destroy(); 167 // } 168 169 // so we do what we can.. 170 if (graphics != null) { 171 // not necessary - already called in AndroidLiveWallpaperService.onDeepPauseApplication 172 // app.graphics.clearManagedCaches(); 173 174 // kill the GLThread managed by GLSurfaceView 175 graphics.onDestroyGLSurfaceView(); 176 177 } 178 179 if (audio != null) { 180 // dispose audio and free native resources, mandatory since graphics.pause is never called in live wallpaper 181 audio.dispose(); 182 } 183 } 184 185 @Override getWindowManager()186 public WindowManager getWindowManager () { 187 return service.getWindowManager(); 188 } 189 getService()190 public AndroidLiveWallpaperService getService () { 191 return service; 192 } 193 194 @Override getApplicationListener()195 public ApplicationListener getApplicationListener () { 196 return listener; 197 } 198 199 @Override postRunnable(Runnable runnable)200 public void postRunnable (Runnable runnable) { 201 synchronized (runnables) { 202 runnables.add(runnable); 203 } 204 } 205 206 @Override getAudio()207 public Audio getAudio () { 208 return audio; 209 } 210 211 @Override getFiles()212 public Files getFiles () { 213 return files; 214 } 215 216 @Override getGraphics()217 public Graphics getGraphics () { 218 return graphics; 219 } 220 221 @Override getInput()222 public AndroidInput getInput () { 223 return input; 224 } 225 226 @Override getNet()227 public Net getNet () { 228 return net; 229 } 230 231 @Override getType()232 public ApplicationType getType () { 233 return ApplicationType.Android; 234 } 235 236 @Override getVersion()237 public int getVersion () { 238 return android.os.Build.VERSION.SDK_INT; 239 } 240 241 @Override getJavaHeap()242 public long getJavaHeap () { 243 return Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory(); 244 } 245 246 @Override getNativeHeap()247 public long getNativeHeap () { 248 return Debug.getNativeHeapAllocatedSize(); 249 } 250 251 @Override getPreferences(String name)252 public Preferences getPreferences (String name) { 253 return new AndroidPreferences(service.getSharedPreferences(name, Context.MODE_PRIVATE)); 254 } 255 256 @Override getClipboard()257 public Clipboard getClipboard () { 258 return clipboard; 259 } 260 261 @Override debug(String tag, String message)262 public void debug (String tag, String message) { 263 if (logLevel >= LOG_DEBUG) { 264 Log.d(tag, message); 265 } 266 } 267 268 @Override debug(String tag, String message, Throwable exception)269 public void debug (String tag, String message, Throwable exception) { 270 if (logLevel >= LOG_DEBUG) { 271 Log.d(tag, message, exception); 272 } 273 } 274 275 @Override log(String tag, String message)276 public void log (String tag, String message) { 277 if (logLevel >= LOG_INFO) Log.i(tag, message); 278 } 279 280 @Override log(String tag, String message, Throwable exception)281 public void log (String tag, String message, Throwable exception) { 282 if (logLevel >= LOG_INFO) Log.i(tag, message, exception); 283 } 284 285 @Override error(String tag, String message)286 public void error (String tag, String message) { 287 if (logLevel >= LOG_ERROR) Log.e(tag, message); 288 } 289 290 @Override error(String tag, String message, Throwable exception)291 public void error (String tag, String message, Throwable exception) { 292 if (logLevel >= LOG_ERROR) Log.e(tag, message, exception); 293 } 294 295 @Override setLogLevel(int logLevel)296 public void setLogLevel (int logLevel) { 297 this.logLevel = logLevel; 298 } 299 300 @Override getLogLevel()301 public int getLogLevel () { 302 return logLevel; 303 } 304 305 @Override exit()306 public void exit () { 307 // no-op 308 } 309 310 @Override addLifecycleListener(LifecycleListener listener)311 public void addLifecycleListener (LifecycleListener listener) { 312 synchronized (lifecycleListeners) { 313 lifecycleListeners.add(listener); 314 } 315 } 316 317 @Override removeLifecycleListener(LifecycleListener listener)318 public void removeLifecycleListener (LifecycleListener listener) { 319 synchronized (lifecycleListeners) { 320 lifecycleListeners.removeValue(listener, true); 321 } 322 } 323 324 @Override getContext()325 public Context getContext () { 326 return service; 327 } 328 329 @Override getRunnables()330 public Array<Runnable> getRunnables () { 331 return runnables; 332 } 333 334 @Override getExecutedRunnables()335 public Array<Runnable> getExecutedRunnables () { 336 return executedRunnables; 337 } 338 339 @Override getLifecycleListeners()340 public SnapshotArray<LifecycleListener> getLifecycleListeners () { 341 return lifecycleListeners; 342 } 343 344 @Override startActivity(Intent intent)345 public void startActivity (Intent intent) { 346 service.startActivity(intent); 347 } 348 349 @Override getApplicationWindow()350 public Window getApplicationWindow () { 351 throw new UnsupportedOperationException(); 352 } 353 354 @Override getHandler()355 public Handler getHandler () { 356 throw new UnsupportedOperationException(); 357 } 358 359 @Override runOnUiThread(Runnable runnable)360 public void runOnUiThread (Runnable runnable) { 361 if (Looper.myLooper() != Looper.getMainLooper()) { 362 // The current thread is not the UI thread. 363 // Let's post the runnable to the event queue of the UI thread. 364 new Handler(Looper.getMainLooper()).post(runnable); 365 } else { 366 // The current thread is the UI thread already. 367 // Let's execute the runnable immediately. 368 runnable.run(); 369 } 370 } 371 372 @Override useImmersiveMode(boolean b)373 public void useImmersiveMode (boolean b) { 374 throw new UnsupportedOperationException(); 375 } 376 377 } 378