Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright 2013 Jaroslaw Wisniewski
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language
* governing permissions and limitations under the License.
*
*/
package com.badlogic.gdx.backends.android;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
import android.service.wallpaper.WallpaperService;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.WindowManager;
import android.app.WallpaperColors;
import com.badlogic.gdx.Application;
import com.badlogic.gdx.ApplicationListener;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Graphics;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.utils.GdxNativesLoader;
/** An implementation of the {@link Application} interface dedicated for android live wallpapers.
*
* Derive from this class. In the {@link AndroidLiveWallpaperService#onCreateApplication} method call the
* {@link AndroidLiveWallpaperService#initialize(ApplicationListener)} method specifying the configuration for the GLSurfaceView.
* You can also use {@link AndroidWallpaperListener} along with {@link ApplicationListener} to respond for wallpaper specific
* events in your app listener:
*
* MyAppListener implements ApplicationListener, AndroidWallpaperListener
*
* Notice: Following methods are not called for live wallpapers: {@link ApplicationListener#pause()}
* {@link ApplicationListener#dispose()} TODO add callbacks to AndroidWallpaperListener allowing to notify app listener about
* changed visibility state of live wallpaper but called from main thread, not from GL thread: for example:
* AndroidWallpaperListener.visibilityChanged(boolean)
*
* //obsoleted: //Notice! //You have to kill all not daemon threads you created in {@link ApplicationListener#pause()} method. //
* {@link ApplicationListener#dispose()} is never called! //If you leave live non daemon threads, wallpaper service wouldn't be
* able to close, //this can cause problems with wallpaper lifecycle.
*
* Notice #2! On some devices wallpaper service is not killed immediately after exiting from preview. Service object is destroyed
* (onDestroy called) but process on which it runs remains alive. When user comes back to wallpaper preview, new wallpaper service
* object is created, but in the same process. It is important if you plan to use static variables / objects - they will be shared
* between living instances of wallpaper services'! And depending on your implementation - it can cause problems you were not
* prepared to.
*
* @author Jaroslaw Wisniewski */
public abstract class AndroidLiveWallpaperService extends WallpaperService {
static {
GdxNativesLoader.load();
}
static final String TAG = "WallpaperService";
static boolean DEBUG = false; // TODO remember to disable this
// instance of libGDX Application, acts as singleton - one instance per application (per WallpaperService)
protected volatile AndroidLiveWallpaper app = null; // can be accessed from GL render thread
protected SurfaceHolder.Callback view = null;
// current format of surface (one GLSurfaceView is shared between all engines)
protected int viewFormat;
protected int viewWidth;
protected int viewHeight;
// app is initialized when engines == 1 first time, app is destroyed in WallpaperService.onDestroy, but
// ApplicationListener.dispose is not called for wallpapers
protected int engines = 0;
protected int visibleEngines = 0;
// engine currently associated with app instance, linked engine serves surface handler for GLSurfaceView
protected volatile AndroidWallpaperEngine linkedEngine = null; // can be accessed from GL render thread by getSurfaceHolder
protected void setLinkedEngine (AndroidWallpaperEngine linkedEngine) {
synchronized (sync) {
this.linkedEngine = linkedEngine;
}
}
// if preview state notified ever
protected volatile boolean isPreviewNotified = false;
// the value of last preview state notified to app listener
protected volatile boolean notifiedPreviewState = false;
volatile int[] sync = new int[0];
// volatile ReentrantLock lock = new ReentrantLock();
// lifecycle methods - the order of calling (flow) is maintained ///////////////
public AndroidLiveWallpaperService () {
super();
}
/** Service is starting, libGDX application is shutdown now */
@Override
public void onCreate () {
if (DEBUG) Log.d(TAG, " > AndroidLiveWallpaperService - onCreate() " + hashCode());
Log.i(TAG, "service created");
super.onCreate();
}
/** One of wallpaper engines is starting. Do not override this method, service manages them internally. */
@Override
public Engine onCreateEngine () {
if (DEBUG) Log.d(TAG, " > AndroidLiveWallpaperService - onCreateEngine()");
Log.i(TAG, "engine created");
return new AndroidWallpaperEngine();
}
/** libGDX application is starting, it occurs after first wallpaper engine had started. Override this method an invoke
* {@link AndroidLiveWallpaperService#initialize(ApplicationListener, AndroidApplicationConfiguration)} from there. */
public void onCreateApplication () {
if (DEBUG) Log.d(TAG, " > AndroidLiveWallpaperService - onCreateApplication()");
}
/** Look at {@link AndroidLiveWallpaperService#initialize(ApplicationListener, AndroidApplicationConfiguration)}
* @param listener */
public void initialize (ApplicationListener listener) {
AndroidApplicationConfiguration config = new AndroidApplicationConfiguration();
initialize(listener, config);
}
/** This method has to be called in the {@link AndroidLiveWallpaperService#onCreateApplication} 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.
*
* @param listener the {@link ApplicationListener} implementing the program logic
* @param config the {@link AndroidApplicationConfiguration}, defining various settings of the application (use accelerometer,
* etc.). Do not change contents of this object after passing to this method! */
public void initialize (ApplicationListener listener, AndroidApplicationConfiguration config) {
if (DEBUG) Log.d(TAG, " > AndroidLiveWallpaperService - initialize()");
app.initialize(listener, config);
if (config.getTouchEventsForLiveWallpaper && Integer.parseInt(android.os.Build.VERSION.SDK) >= 7)
linkedEngine.setTouchEventsEnabled(true);
// onResume(); do not call it there
}
/** Getter for SurfaceHolder object, surface holder is required to restore gl context in GLSurfaceView */
public SurfaceHolder getSurfaceHolder () {
if (DEBUG) Log.d(TAG, " > AndroidLiveWallpaperService - getSurfaceHolder()");
synchronized (sync) {
if (linkedEngine == null)
return null;
else
return linkedEngine.getSurfaceHolder();
}
}
// engines live there
/** Called when the last engine is ending its live, it can occur when: 1. service is dying 2. service is switching from one
* engine to another 3. [only my assumption] when wallpaper is not visible and system is going to restore some memory for
* foreground processing by disposing not used wallpaper engine We can't destroy app there, because: 1. in won't work - gl
* context is disposed right now and after app.onDestroy() app would stuck somewhere in gl thread synchronizing code 2. we
* don't know if service create more engines, app is shared between them and should stay initialized waiting for new engines */
public void onDeepPauseApplication () {
if (DEBUG) Log.d(TAG, " > AndroidLiveWallpaperService - onDeepPauseApplication()");
// free native resources consuming runtime memory, note that it can cause some lag when resuming wallpaper
if (app != null) {
app.graphics.clearManagedCaches();
}
}
/** Service is dying, and will not be used again. You have to finish execution off all living threads there or short after
* there, besides the new wallpaper service wouldn't be able to start. */
@Override
public void onDestroy () {
if (DEBUG) Log.d(TAG, " > AndroidLiveWallpaperService - onDestroy() " + hashCode());
Log.i(TAG, "service destroyed");
super.onDestroy(); // can call engine.onSurfaceDestroyed, must be before bellow code:
if (app != null) {
app.onDestroy();
app = null;
view = null;
}
}
@Override
protected void finalize () throws Throwable {
Log.i(TAG, "service finalized");
super.finalize();
}
// end of lifecycle methods ////////////////////////////////////////////////////////
public AndroidLiveWallpaper getLiveWallpaper () {
return app;
}
public WindowManager getWindowManager () {
return (WindowManager)getSystemService(Context.WINDOW_SERVICE);
}
/** Bridge between surface on which wallpaper is rendered and the wallpaper service. The problem is that there can be a group of
* Engines at one time and we must share libGDX application between them.
*
* @author libGDX team and Jaroslaw Wisniewski */
public class AndroidWallpaperEngine extends Engine {
protected boolean engineIsVisible = false;
// destination format of surface when this engine is active (updated in onSurfaceChanged)
protected int engineFormat;
protected int engineWidth;
protected int engineHeight;
// lifecycle methods - the order of calling (flow) is maintained /////////////////
public AndroidWallpaperEngine () {
if (DEBUG) Log.d(TAG, " > AndroidWallpaperEngine() " + hashCode());
}
@Override
public void onCreate (final SurfaceHolder surfaceHolder) {
if (DEBUG)
Log.d(TAG, " > AndroidWallpaperEngine - onCreate() " + hashCode() + " running: " + engines + ", linked: "
+ (linkedEngine == this) + ", thread: " + Thread.currentThread().toString());
super.onCreate(surfaceHolder);
}
/** Called before surface holder callbacks (ex for GLSurfaceView)! This is called immediately after the surface is first
* created. Implementations of this should start up whatever rendering code they desire. Note that only one thread can ever
* draw into a Surface, so you should not draw into the Surface here if your normal rendering will be in another thread. */
@Override
public void onSurfaceCreated (final SurfaceHolder holder) {
engines++;
setLinkedEngine(this);
if (DEBUG)
Log.d(TAG, " > AndroidWallpaperEngine - onSurfaceCreated() " + hashCode() + ", running: " + engines + ", linked: "
+ (linkedEngine == this));
Log.i(TAG, "engine surface created");
super.onSurfaceCreated(holder);
if (engines == 1) {
// safeguard: recover attributes that could suffered by unexpected surfaceDestroy event
visibleEngines = 0;
}
if (engines == 1 && app == null) {
viewFormat = 0; // must be initialized with zeroes
viewWidth = 0;
viewHeight = 0;
app = new AndroidLiveWallpaper(AndroidLiveWallpaperService.this);
onCreateApplication();
if (app.graphics == null)
throw new Error(
"You must override 'AndroidLiveWallpaperService.onCreateApplication' method and call 'initialize' from its body.");
}
view = (SurfaceHolder.Callback)app.graphics.view;
this.getSurfaceHolder().removeCallback(view); // we are going to call this events manually
// inherit format from shared surface view
engineFormat = viewFormat;
engineWidth = viewWidth;
engineHeight = viewHeight;
if (engines == 1) {
view.surfaceCreated(holder);
} else {
// this combination of methods is described in AndroidWallpaperEngine.onResume
view.surfaceDestroyed(holder);
notifySurfaceChanged(engineFormat, engineWidth, engineHeight, false);
view.surfaceCreated(holder);
}
notifyPreviewState();
notifyOffsetsChanged();
if (!Gdx.graphics.isContinuousRendering()) {
Gdx.graphics.requestRendering();
}
}
/** This is called immediately after any structural changes (format or size) have been made to the surface. You should at
* this point update the imagery in the surface. This method is always called at least once, after
* surfaceCreated(SurfaceHolder). */
@Override
public void onSurfaceChanged (final SurfaceHolder holder, final int format, final int width, final int height) {
if (DEBUG)
Log.d(TAG, " > AndroidWallpaperEngine - onSurfaceChanged() isPreview: " + isPreview() + ", " + hashCode()
+ ", running: " + engines + ", linked: " + (linkedEngine == this) + ", sufcace valid: "
+ getSurfaceHolder().getSurface().isValid());
Log.i(TAG, "engine surface changed");
super.onSurfaceChanged(holder, format, width, height);
notifySurfaceChanged(format, width, height, true);
// it shouldn't be required there (as I understand android.service.wallpaper.WallpaperService impl)
// notifyPreviewState();
}
/** Notifies shared GLSurfaceView about changed surface format.
* @param format
* @param width
* @param height
* @param forceUpdate if false, surface view will be notified only if currently contains expired information */
private void notifySurfaceChanged (final int format, final int width, final int height, boolean forceUpdate) {
if (!forceUpdate && format == viewFormat && width == viewWidth && height == viewHeight) {
// skip if didn't changed
if (DEBUG) Log.d(TAG, " > surface is current, skipping surfaceChanged event");
} else {
// update engine desired surface format
engineFormat = format;
engineWidth = width;
engineHeight = height;
// update surface view if engine is linked with it already
if (linkedEngine == this) {
viewFormat = engineFormat;
viewWidth = engineWidth;
viewHeight = engineHeight;
view.surfaceChanged(this.getSurfaceHolder(), viewFormat, viewWidth, viewHeight);
} else {
if (DEBUG) Log.d(TAG, " > engine is not active, skipping surfaceChanged event");
}
}
}
/** Called to inform you of the wallpaper becoming visible or hidden. It is very important that a wallpaper only use CPU
* while it is visible.. */
@Override
public void onVisibilityChanged (final boolean visible) {
boolean reportedVisible = isVisible();
if (DEBUG)
Log.d(TAG, " > AndroidWallpaperEngine - onVisibilityChanged(paramVisible: " + visible + " reportedVisible: "
+ reportedVisible + ") " + hashCode() + ", sufcace valid: " + getSurfaceHolder().getSurface().isValid());
super.onVisibilityChanged(visible);
// Android WallpaperService sends fake visibility changed events to force some buggy live wallpapers to shut down after
// onSurfaceChanged when they aren't visible, it can cause problems in current implementation and it is not necessary
if (reportedVisible == false && visible == true) {
if (DEBUG) Log.d(TAG, " > fake visibilityChanged event! Android WallpaperService likes do that!");
return;
}
notifyVisibilityChanged(visible);
}
private void notifyVisibilityChanged (final boolean visible) {
if (this.engineIsVisible != visible) {
this.engineIsVisible = visible;
if (this.engineIsVisible)
onResume();
else
onPause();
} else {
if (DEBUG) Log.d(TAG, " > visible state is current, skipping visibilityChanged event!");
}
}
public void onResume () {
visibleEngines++;
if (DEBUG)
Log.d(TAG, " > AndroidWallpaperEngine - onResume() " + hashCode() + ", running: " + engines + ", linked: "
+ (linkedEngine == this) + ", visible: " + visibleEngines);
Log.i(TAG, "engine resumed");
if (linkedEngine != null) {
if (linkedEngine != this) {
setLinkedEngine(this);
// disconnect surface view from previous window
view.surfaceDestroyed(this.getSurfaceHolder()); // force gl surface reload, new instance will be created on current
// surface holder
// resize surface to match window associated with current engine
notifySurfaceChanged(engineFormat, engineWidth, engineHeight, false);
// connect surface view to current engine
view.surfaceCreated(this.getSurfaceHolder());
} else {
// update if surface changed when engine wasn't active
notifySurfaceChanged(engineFormat, engineWidth, engineHeight, false);
}
if (visibleEngines == 1) app.onResume();
notifyPreviewState();
notifyOffsetsChanged();
if (!Gdx.graphics.isContinuousRendering()) {
Gdx.graphics.requestRendering();
}
}
}
public void onPause () {
visibleEngines--;
if (DEBUG)
Log.d(TAG, " > AndroidWallpaperEngine - onPause() " + hashCode() + ", running: " + engines + ", linked: "
+ (linkedEngine == this) + ", visible: " + visibleEngines);
Log.i(TAG, "engine paused");
// this shouldn't never happen, but if it will.. live wallpaper will not be stopped when device will pause and lwp will
// drain battery.. shortly!
if (visibleEngines >= engines) {
Log.e(AndroidLiveWallpaperService.TAG, "wallpaper lifecycle error, counted too many visible engines! repairing..");
visibleEngines = Math.max(engines - 1, 0);
}
if (linkedEngine != null) {
if (visibleEngines == 0) app.onPause();
}
if (DEBUG) Log.d(TAG, " > AndroidWallpaperEngine - onPause() done!");
}
/** Called after surface holder callbacks (ex for GLSurfaceView)! This is called immediately before a surface is being
* destroyed. After returning from this call, you should no longer try to access this surface. If you have a rendering
* thread that directly accesses the surface, you must ensure that thread is no longer touching the Surface before returning
* from this function.
*
* Attention! In some cases GL context may be shutdown right now! and SurfaceHolder.Surface.isVaild = false */
@Override
public void onSurfaceDestroyed (final SurfaceHolder holder) {
engines--;
if (DEBUG)
Log.d(TAG, " > AndroidWallpaperEngine - onSurfaceDestroyed() " + hashCode() + ", running: " + engines + " ,linked: "
+ (linkedEngine == this) + ", isVisible: " + engineIsVisible);
Log.i(TAG, "engine surface destroyed");
// application can be in resumed state at this moment if app surface had been lost just after it was created (wallpaper
// selected too fast from preview mode etc)
// it is too late probably - calling on pause causes deadlock
// notifyVisibilityChanged(false);
// it is too late to call app.onDispose, just free native resources
if (engines == 0) onDeepPauseApplication();
// free surface if it belongs to this engine and if it was initialized
if (linkedEngine == this && view != null) view.surfaceDestroyed(holder);
// waitingSurfaceChangedEvent = null;
engineFormat = 0;
engineWidth = 0;
engineHeight = 0;
// safeguard for other engine callbacks
if (engines == 0) linkedEngine = null;
super.onSurfaceDestroyed(holder);
}
@Override
public void onDestroy () {
super.onDestroy();
}
// end of lifecycle methods ////////////////////////////////////////////////////////
// input
boolean iconDropConsumed = true;
int xIconDrop, yIconDrop;
@Override
public Bundle onCommand (final String pAction, final int pX, final int pY, final int pZ, final Bundle pExtras,
final boolean pResultRequested) {
if (DEBUG)
Log.d(TAG, " > AndroidWallpaperEngine - onCommand(" + pAction + " " + pX + " " + pY + " " + pZ + " " + pExtras + " "
+ pResultRequested + ")" + ", linked: " + (linkedEngine == this));
if (pAction.equals("android.home.drop")){
iconDropConsumed = false;
xIconDrop = pX;
yIconDrop = pY;
notifyIconDropped();
}
return super.onCommand(pAction, pX, pY, pZ, pExtras, pResultRequested);
}
protected void notifyIconDropped () {
if (linkedEngine == this && app.listener instanceof AndroidWallpaperListener) {
if (!iconDropConsumed) { // same type of synchronization as in notifyOffsetsChanged()
iconDropConsumed = true;
app.postRunnable(new Runnable() {
@Override
public void run () {
boolean isCurrent = false;
synchronized (sync) {
isCurrent = (linkedEngine == AndroidWallpaperEngine.this);
}
if (isCurrent)
((AndroidWallpaperListener)app.listener).iconDropped(xIconDrop, yIconDrop);
}
});
}
}
}
@Override
public void onTouchEvent (MotionEvent event) {
if (linkedEngine == this) {
app.input.onTouch(null, event);
}
}
// offsets from last onOffsetsChanged
boolean offsetsConsumed = true;
float xOffset = 0.0f;
float yOffset = 0.0f;
float xOffsetStep = 0.0f;
float yOffsetStep = 0.0f;
int xPixelOffset = 0;
int yPixelOffset = 0;
@Override
public void onOffsetsChanged (final float xOffset, final float yOffset, final float xOffsetStep, final float yOffsetStep,
final int xPixelOffset, final int yPixelOffset) {
// it spawns too frequent on some devices - its annoying!
// if (DEBUG)
// Log.d(TAG, " > AndroidWallpaperEngine - onOffsetChanged(" + xOffset + " " + yOffset + " " + xOffsetStep + " "
// + yOffsetStep + " " + xPixelOffset + " " + yPixelOffset + ") " + hashCode() + ", linkedApp: " + (linkedApp != null));
this.offsetsConsumed = false;
this.xOffset = xOffset;
this.yOffset = yOffset;
this.xOffsetStep = xOffsetStep;
this.yOffsetStep = yOffsetStep;
this.xPixelOffset = xPixelOffset;
this.yPixelOffset = yPixelOffset;
// can fail if linkedApp == null, so we repeat it in Engine.onResume
notifyOffsetsChanged();
if (!Gdx.graphics.isContinuousRendering()) {
Gdx.graphics.requestRendering();
}
super.onOffsetsChanged(xOffset, yOffset, xOffsetStep, yOffsetStep, xPixelOffset, yPixelOffset);
}
protected void notifyOffsetsChanged () {
if (linkedEngine == this && app.listener instanceof AndroidWallpaperListener) {
if (!offsetsConsumed) { // no need for more sophisticated synchronization - offsetsChanged can be called multiple
// times and with various patterns on various devices - user application must be prepared for that
offsetsConsumed = true;
app.postRunnable(new Runnable() {
@Override
public void run () {
boolean isCurrent = false;
synchronized (sync) {
isCurrent = (linkedEngine == AndroidWallpaperEngine.this); // without this app can crash when fast
// switching between engines (tested!)
}
if (isCurrent)
((AndroidWallpaperListener)app.listener).offsetChange(xOffset, yOffset, xOffsetStep, yOffsetStep,
xPixelOffset, yPixelOffset);
}
});
}
}
}
protected void notifyPreviewState () {
// notify preview state to app listener
if (linkedEngine == this && app.listener instanceof AndroidWallpaperListener) {
final boolean currentPreviewState = linkedEngine.isPreview();
app.postRunnable(new Runnable() {
@Override
public void run () {
boolean shouldNotify = false;
synchronized (sync) {
if (!isPreviewNotified || notifiedPreviewState != currentPreviewState) {
notifiedPreviewState = currentPreviewState;
isPreviewNotified = true;
shouldNotify = true;
}
}
if (shouldNotify) {
AndroidLiveWallpaper currentApp = app; // without this app can crash when fast switching between engines
// (tested!)
if (currentApp != null)
((AndroidWallpaperListener)currentApp.listener).previewStateChange(currentPreviewState);
}
}
});
}
}
@Override
public WallpaperColors onComputeColors () {
Application app = Gdx.app;
if (Build.VERSION.SDK_INT >= 27 && app instanceof AndroidLiveWallpaper) {
AndroidLiveWallpaper liveWallpaper = (AndroidLiveWallpaper) app;
Color[] colors = liveWallpaper.wallpaperColors;
if (colors != null)
return new WallpaperColors(
android.graphics.Color.valueOf(colors[0].r, colors[0].g, colors[0].b, colors[0].a),
android.graphics.Color.valueOf(colors[1].r, colors[1].g, colors[1].b, colors[1].a),
android.graphics.Color.valueOf(colors[2].r, colors[2].g, colors[2].b, colors[2].a)
);
}
return super.onComputeColors();
}
}
}