OSDN Git Service

4a1fc3dcab8d7b3bd9a184e3bc5d557002caae69
[mikumikustudio/libgdx-mikumikustudio.git] / backends / gdx-backend-android / src / com / badlogic / gdx / backends / android / AndroidLiveWallpaperService.java
1 /*\r
2  * Copyright 2013 Jaroslaw Wisniewski <j.wisniewski@appsisle.com>\r
3  * \r
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the\r
5  * License. You may obtain a copy of the License at\r
6  * \r
7  * http://www.apache.org/licenses/LICENSE-2.0\r
8  * \r
9  * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS"\r
10  * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language\r
11  * governing permissions and limitations under the License.\r
12  *\r
13  */\r
14 \r
15 package com.badlogic.gdx.backends.android;\r
16 \r
17 import java.lang.reflect.Method;\r
18 import java.util.concurrent.locks.ReentrantLock;\r
19 \r
20 import com.badlogic.gdx.Application;\r
21 import com.badlogic.gdx.ApplicationListener;\r
22 import com.badlogic.gdx.Gdx;\r
23 import com.badlogic.gdx.Graphics;\r
24 import com.badlogic.gdx.android.AndroidWallpaperListener;\r
25 import com.badlogic.gdx.backends.android.surfaceview.FillResolutionStrategy;\r
26 import com.badlogic.gdx.backends.android.surfaceview.GLBaseSurfaceViewLW;\r
27 import com.badlogic.gdx.backends.android.surfaceview.GLSurfaceViewCupcake;\r
28 import com.badlogic.gdx.graphics.GL10;\r
29 import com.badlogic.gdx.graphics.GL11;\r
30 import com.badlogic.gdx.utils.GdxNativesLoader;\r
31 \r
32 import android.app.Activity;\r
33 import android.app.WallpaperManager;\r
34 import android.content.Context;\r
35 import android.opengl.GLSurfaceView;\r
36 import android.os.Bundle;\r
37 import android.os.Handler;\r
38 import android.provider.LiveFolders;\r
39 import android.service.wallpaper.WallpaperService;\r
40 import android.service.wallpaper.WallpaperService.Engine;\r
41 import android.util.Log;\r
42 import android.view.MotionEvent;\r
43 import android.view.SurfaceHolder;\r
44 import android.view.WindowManager;\r
45 \r
46 \r
47 /** \r
48  * An implementation of the {@link Application} interface dedicated for android live wallpapers.\r
49  * \r
50  * Derive from this class. In the {@link AndroidLiveWallpaperService#onCreateApplication} method call the {@link AndroidLiveWallpaperService#initialize(ApplicationListener, boolean)} \r
51  * method specifying the configuration for the GLSurfaceView. You can also use {@link AndroidWallpaperListener} \r
52  * along with {@link ApplicationListener} to respond for wallpaper specific events in your app listener:\r
53  * \r
54  * MyAppListener implements ApplicationListener, AndroidWallpaperListener\r
55  * \r
56  * Notice:\r
57  * Following methods are not called for live wallpapers:\r
58  * {@link ApplicationListener#pause()}\r
59  * {@link ApplicationListener#dispose()}\r
60  * TODO add callbacks to AndroidWallpaperListener allowing to notify app listener about changed visibility\r
61  * state of live wallpaper but called from main thread, not from GL thread:\r
62  * for example:\r
63  * AndroidWallpaperListener.visibilityChanged(boolean)\r
64  * \r
65  * //obsoleted:\r
66  * //Notice!\r
67  * //You have to kill all not daemon threads you created in {@link ApplicationListener#pause()} method.\r
68  * //{@link ApplicationListener#dispose()} is never called!\r
69  * //If you leave live non daemon threads, wallpaper service wouldn't be able to close, \r
70  * //this can cause problems with wallpaper lifecycle.\r
71  * \r
72  * Notice #2!\r
73  * On some devices wallpaper service is not killed immediately after exiting from preview. Service object \r
74  * is destroyed (onDestroy called) but process on which it runs remains alive. When user comes back to wallpaper\r
75  * preview, new wallpaper service object is created, but in the same process. It is important if you plan to\r
76  * use static variables / objects - they will be shared between living instances of wallpaper services'!\r
77  * And depending on your implementation - it can cause problems you were not prepared to.\r
78  * \r
79  * @author Jaroslaw Wisniewski <j.wisniewski@appsisle.com>\r
80  */\r
81 public abstract class AndroidLiveWallpaperService extends WallpaperService {\r
82         static {\r
83                 GdxNativesLoader.load();\r
84         }\r
85         \r
86         static final String TAG = "WallpaperService";\r
87         static boolean DEBUG    = false;        // TODO remember to disable this\r
88 \r
89         \r
90         // instance of libGDX Application, acts as singleton - one instance per application (per WallpaperService)\r
91         protected volatile AndroidLiveWallpaper app = null;     // can be accessed from GL render thread\r
92         protected SurfaceHolder.Callback view = null;\r
93         \r
94         // current format of surface (one GLSurfaceView is shared between all engines)\r
95         protected int viewFormat;\r
96         protected int viewWidth;\r
97         protected int viewHeight;\r
98         \r
99         // app is initialized when engines == 1 first time, app is destroyed in WallpaperService.onDestroy, but ApplicationListener.dispose is not called for wallpapers\r
100         protected int engines = 0;\r
101         protected int visibleEngines = 0;\r
102         \r
103         // engine currently associated with app instance, linked engine serves surface handler for GLSurfaceView\r
104         protected volatile AndroidWallpaperEngine linkedEngine = null;          // can be accessed from GL render thread by getSurfaceHolder\r
105         \r
106         protected void setLinkedEngine (AndroidWallpaperEngine linkedEngine) {\r
107                 synchronized (sync) {\r
108                         this.linkedEngine = linkedEngine;\r
109                 }\r
110         }\r
111         \r
112         \r
113         // if preview state notified ever\r
114         protected volatile boolean isPreviewNotified = false;\r
115         \r
116         // the value of last preview state notified to app listener\r
117         protected volatile boolean notifiedPreviewState = false;\r
118 \r
119 \r
120         volatile int[] sync = new int[0];\r
121         //volatile ReentrantLock lock = new ReentrantLock();\r
122         \r
123         \r
124         // lifecycle methods - the order of calling (flow) is maintained ///////////////\r
125         \r
126         public AndroidLiveWallpaperService () {\r
127                 super();\r
128         }\r
129 \r
130         \r
131         /**\r
132          * Service is starting, libGDX application is shutdown now\r
133          */\r
134         @Override\r
135         public void onCreate () {\r
136                 if (DEBUG) Log.d(TAG, " > AndroidLiveWallpaperService - onCreate() " + hashCode());\r
137                 Log.i(TAG, "service created");\r
138 \r
139                 super.onCreate();               \r
140         }\r
141         \r
142         \r
143         /**\r
144          * One of wallpaper engines is starting. \r
145          * Do not override this method, service manages them internally.\r
146          */\r
147         @Override\r
148         public Engine onCreateEngine () {\r
149                 if (DEBUG) Log.d(TAG, " > AndroidLiveWallpaperService - onCreateEngine()");\r
150                 Log.i(TAG, "engine created");\r
151                 \r
152                 return new AndroidWallpaperEngine();\r
153         }\r
154         \r
155         \r
156         /**\r
157          * libGDX application is starting, it occurs after first wallpaper engine had started.\r
158          * Override this method an invoke {@link AndroidLiveWallpaperService#initialize(ApplicationListener, AndroidApplicationConfiguration)} from there.\r
159          */\r
160         public void onCreateApplication () {\r
161                 if (DEBUG) Log.d(TAG, " > AndroidLiveWallpaperService - onCreateApplication()");\r
162         }\r
163         \r
164         \r
165         /** \r
166          * Look at {@link AndroidLiveWallpaperService#initialize(ApplicationListener, AndroidApplicationConfiguration)}}\r
167          * @param listener\r
168          * @param useGL2IfAvailable\r
169          */\r
170         public void initialize (ApplicationListener listener, boolean useGL2IfAvailable) {\r
171                 AndroidApplicationConfiguration config = new AndroidApplicationConfiguration();\r
172                 config.useGL20 = useGL2IfAvailable;\r
173                 initialize(listener, config);\r
174         }\r
175 \r
176         /** \r
177          * This method has to be called in the {@link AndroidLiveWallpaperService#onCreateApplication} method. It sets up all the things necessary to get\r
178          * input, render via OpenGL and so on. If config.useGL20 is set the AndroidApplication will try to create an OpenGL ES 2.0\r
179          * context which can then be used via {@link Graphics#getGL20()}. The {@link GL10} and {@link GL11} interfaces should not be\r
180          * used when OpenGL ES 2.0 is enabled. To query whether enabling OpenGL ES 2.0 was successful use the\r
181          * {@link Graphics#isGL20Available()} method. You can configure other aspects of the application with the rest of the fields in\r
182          * the {@link AndroidApplicationConfiguration} instance.\r
183          * \r
184          * @param listener the {@link ApplicationListener} implementing the program logic\r
185          * @param config the {@link AndroidApplicationConfiguration}, defining various settings of the application (use accelerometer,\r
186          *           etc.). Do not change contents of this object after passing to this method!\r
187          */\r
188         public void initialize (ApplicationListener listener, AndroidApplicationConfiguration config) {\r
189                 if (DEBUG) Log.d(TAG, " > AndroidLiveWallpaperService - initialize()");\r
190                 \r
191                 app.initialize(listener, config);\r
192                 \r
193                 if (config.getTouchEventsForLiveWallpaper && Integer.parseInt(android.os.Build.VERSION.SDK) >= 7)\r
194                         linkedEngine.setTouchEventsEnabled(true);\r
195                 \r
196                 //onResume(); do not call it there\r
197         }\r
198         \r
199         \r
200         /**\r
201          * Getter for SurfaceHolder object, surface holder is required to restore gl context in GLSurfaceView\r
202          */\r
203         public SurfaceHolder getSurfaceHolder() {\r
204                 if (DEBUG) Log.d(TAG, " > AndroidLiveWallpaperService - getSurfaceHolder()");\r
205                 \r
206                 synchronized (sync) {\r
207                         if (linkedEngine == null)\r
208                                 return null;\r
209                         else \r
210                                 return linkedEngine.getSurfaceHolder();\r
211                 }\r
212         }\r
213         \r
214         \r
215         // engines live there\r
216         \r
217         \r
218         /**\r
219          * Called when the last engine is ending its live, it can occur when:\r
220          * 1. service is dying\r
221          * 2. service is switching from one engine to another\r
222          * 3. [only my assumption] when wallpaper is not visible and system is going to restore some memory \r
223          *      for foreground processing by disposing not used wallpaper engine\r
224          * We can't destroy app there, because:\r
225          * 1. in won't work - gl context is disposed right now and after app.onDestroy() app would stuck somewhere in gl thread synchronizing code\r
226          * 2. we don't know if service create more engines, app is shared between them and should stay initialized waiting for new engines\r
227          */\r
228         public void onDeepPauseApplication () {\r
229                 if (DEBUG) Log.d(TAG, " > AndroidLiveWallpaperService - onDeepPauseApplication()");\r
230                 \r
231                 // free native resources consuming runtime memory, note that it can cause some lag when resuming wallpaper\r
232                 if (app != null) {\r
233                         app.graphics.clearManagedCaches();\r
234                 }\r
235         }\r
236         \r
237         \r
238         /**\r
239          * Service is dying, and will not be used again.\r
240          * You have to finish execution off all living threads there or short after there, \r
241          * besides the new wallpaper service wouldn't be able to start.\r
242          */\r
243         @Override\r
244         public void onDestroy () {\r
245                 if (DEBUG) Log.d(TAG, " > AndroidLiveWallpaperService - onDestroy() " + hashCode());\r
246                 Log.i(TAG, "service destroyed");\r
247                 \r
248                 super.onDestroy();      // can call engine.onSurfaceDestroyed, must be before bellow code:\r
249                 \r
250                 if (app != null) {\r
251                         app.onDestroy();\r
252                         \r
253                         app = null;\r
254                         view = null;\r
255                 }\r
256         }\r
257         \r
258         \r
259         @Override\r
260         protected void finalize () throws Throwable {\r
261                 Log.i(TAG, "service finalized");\r
262                 super.finalize();\r
263         }\r
264         \r
265         // end of lifecycle methods ////////////////////////////////////////////////////////\r
266         \r
267         \r
268         \r
269         public AndroidLiveWallpaper getLiveWallpaper() {\r
270                 return app;\r
271         }\r
272         \r
273         \r
274         public WindowManager getWindowManager() {\r
275                 return (WindowManager)getSystemService(Context.WINDOW_SERVICE);\r
276         }\r
277         \r
278         \r
279         /**\r
280          * Bridge between surface on which wallpaper is rendered and the wallpaper service. \r
281          * The problem is that there can be a group of Engines at one time and we must share libGDX application between them.\r
282          * \r
283          * @author libGDX team and Jaroslaw Wisniewski <j.wisniewski@appsisle.com>\r
284          *\r
285          */\r
286         public class AndroidWallpaperEngine extends Engine {\r
287 \r
288                 protected boolean engineIsVisible = false;\r
289                 \r
290                 // destination format of surface when this engine is active (updated in onSurfaceChanged)\r
291                 protected int engineFormat;\r
292                 protected int engineWidth;\r
293                 protected int engineHeight;\r
294                 \r
295                 \r
296                 // lifecycle methods - the order of calling (flow) is maintained /////////////////\r
297                 \r
298                 public AndroidWallpaperEngine () {\r
299                         if (DEBUG) Log.d(TAG, " > AndroidWallpaperEngine() " + hashCode());\r
300                 }\r
301 \r
302                 \r
303                 @Override\r
304                 public void onCreate (final SurfaceHolder surfaceHolder) {\r
305                         if (DEBUG) Log.d(TAG, " > AndroidWallpaperEngine - onCreate() " + hashCode() + " running: " + engines + ", linked: " + (linkedEngine == this) + ", thread: " + Thread.currentThread().toString());\r
306                         super.onCreate(surfaceHolder);\r
307                 }\r
308                 \r
309                 \r
310                 /**\r
311                  * Called before surface holder callbacks (ex for GLSurfaceView)!\r
312                  * This is called immediately after the surface is first created. Implementations of this should start \r
313                  * up whatever rendering code they desire. Note that only one thread can ever draw into a Surface, \r
314                  * so you should not draw into the Surface here if your normal rendering will be in another thread.\r
315                  */\r
316                 @Override\r
317                 public void onSurfaceCreated (final SurfaceHolder holder) {\r
318                         engines ++;\r
319                         setLinkedEngine(this);\r
320                         \r
321                         if (DEBUG) Log.d(TAG, " > AndroidWallpaperEngine - onSurfaceCreated() " + hashCode() + ", running: " + engines + ", linked: " + (linkedEngine == this));\r
322                         Log.i(TAG, "engine surface created");\r
323                         \r
324                         super.onSurfaceCreated(holder);\r
325         \r
326                         if (engines == 1) {\r
327                                 // safeguard: recover attributes that could suffered by unexpected surfaceDestroy event\r
328                                 visibleEngines = 0;\r
329                         }\r
330                         \r
331                         if (engines == 1 && app == null) {\r
332                                 viewFormat = 0; // must be initialized with zeroes\r
333                                 viewWidth = 0;\r
334                                 viewHeight = 0;\r
335                                 \r
336                                 app = new AndroidLiveWallpaper(AndroidLiveWallpaperService.this);\r
337                                 \r
338                                 onCreateApplication();\r
339                                 if (app.graphics == null)\r
340                                         throw new Error("You must override 'AndroidLiveWallpaperService.onCreateApplication' method and call 'initialize' from its body.");\r
341                         }\r
342 \r
343                         view = (SurfaceHolder.Callback)app.graphics.view;\r
344                         this.getSurfaceHolder().removeCallback(view);   // we are going to call this events manually\r
345 \r
346                         // inherit format from shared surface view\r
347                         engineFormat = viewFormat;\r
348                         engineWidth = viewWidth;\r
349                         engineHeight = viewHeight;\r
350 \r
351                         if (engines == 1) {\r
352                                 view.surfaceCreated(holder);\r
353                         }\r
354                         else {\r
355                                 // this combination of methods is described in AndroidWallpaperEngine.onResume\r
356                                 view.surfaceDestroyed(holder);\r
357                                 notifySurfaceChanged(engineFormat, engineWidth, engineHeight, false);\r
358                                 view.surfaceCreated(holder);\r
359                         }\r
360                         \r
361                         notifyPreviewState();\r
362                         notifyOffsetsChanged();\r
363                 }\r
364                 \r
365                 \r
366                 /**\r
367                  * This is called immediately after any structural changes (format or size) have been made to the surface. \r
368                  * You should at this point update the imagery in the surface. This method is always called at least once, \r
369                  * after surfaceCreated(SurfaceHolder).\r
370                  */\r
371                 @Override\r
372                 public void onSurfaceChanged (final SurfaceHolder holder, final int format, final int width, final int height) {\r
373                         if (DEBUG) Log.d(TAG, " > AndroidWallpaperEngine - onSurfaceChanged() isPreview: " + isPreview() + ", " + hashCode() + ", running: " + engines + ", linked: " + (linkedEngine == this) + ", sufcace valid: " + getSurfaceHolder().getSurface().isValid());\r
374                         Log.i(TAG, "engine surface changed");\r
375                         \r
376                         super.onSurfaceChanged(holder, format, width, height);\r
377                         \r
378                         notifySurfaceChanged(format, width, height, true);\r
379                         \r
380                         // it shouldn't be required there (as I understand android.service.wallpaper.WallpaperService impl)\r
381                         //notifyPreviewState();\r
382                 }\r
383 \r
384                 \r
385                 /**\r
386                  * Notifies shared GLSurfaceView about changed surface format.\r
387                  * @param format\r
388                  * @param width\r
389                  * @param height\r
390                  * @param forceUpdate if false, surface view will be notified only if currently contains expired information\r
391                  */\r
392                 private void notifySurfaceChanged(final int format, final int width, final int height, boolean forceUpdate)\r
393                 {\r
394                         if (!forceUpdate && format == viewFormat && width == viewWidth && height == viewHeight) {\r
395                                 // skip if didn't changed\r
396                                 if (DEBUG) Log.d(TAG, " > surface is current, skipping surfaceChanged event");\r
397                         }\r
398                         else {\r
399                                 // update engine desired surface format\r
400                                 engineFormat = format;\r
401                                 engineWidth = width;\r
402                                 engineHeight = height;\r
403                                 \r
404                                 // update surface view if engine is linked with it already\r
405                                 if (linkedEngine == this) {\r
406                                         viewFormat = engineFormat;\r
407                                         viewWidth = engineWidth;\r
408                                         viewHeight = engineHeight;\r
409                                         view.surfaceChanged(this.getSurfaceHolder(), viewFormat, viewWidth, viewHeight);\r
410                                 }\r
411                                 else {\r
412                                         if (DEBUG) Log.d(TAG, " > engine is not active, skipping surfaceChanged event");\r
413                                 }\r
414                         }\r
415                 }\r
416                 \r
417 \r
418                 /**\r
419                  * Called to inform you of the wallpaper becoming visible or hidden. It is very important that \r
420                  * a wallpaper only use CPU while it is visible..\r
421                  */\r
422                 @Override\r
423                 public void onVisibilityChanged (final boolean visible) {\r
424                         boolean reportedVisible = isVisible();\r
425 \r
426                         if (DEBUG) Log.d(TAG, " > AndroidWallpaperEngine - onVisibilityChanged(paramVisible: " + visible + " reportedVisible: " + reportedVisible + ") " + hashCode()  + ", sufcace valid: " + getSurfaceHolder().getSurface().isValid());\r
427                         super.onVisibilityChanged(visible);\r
428 \r
429                         // 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\r
430                         if (reportedVisible == false && visible == true) {\r
431                                 if (DEBUG) Log.d(TAG, " > fake visibilityChanged event! Android WallpaperService likes do that!");\r
432                                 return;\r
433                         }\r
434 \r
435                         notifyVisibilityChanged(visible);\r
436                 }\r
437 \r
438                 \r
439                 private void notifyVisibilityChanged(final boolean visible)\r
440                 {\r
441                         if (this.engineIsVisible != visible) {\r
442                                 this.engineIsVisible = visible;\r
443                                 \r
444                                 if (this.engineIsVisible)\r
445                                         onResume();\r
446                                 else\r
447                                         onPause();\r
448                         }\r
449                         else {\r
450                                 if (DEBUG) Log.d(TAG, " > visible state is current, skipping visibilityChanged event!");\r
451                         }\r
452                 }\r
453                 \r
454                 \r
455                 public void onResume () {\r
456                         visibleEngines ++;\r
457                         if (DEBUG) Log.d(TAG, " > AndroidWallpaperEngine - onResume() " + hashCode() + ", running: " + engines + ", linked: " + (linkedEngine == this) + ", visible: " + visibleEngines);\r
458                         Log.i(TAG, "engine resumed");\r
459                         \r
460                         if (linkedEngine != null) {\r
461                                 if (linkedEngine != this) {\r
462                                         setLinkedEngine(this);\r
463                                         \r
464                                         // disconnect surface view from previous window\r
465                                         view.surfaceDestroyed(this.getSurfaceHolder()); // force gl surface reload, new instance will be created on current surface holder\r
466                                         \r
467                                         // resize surface to match window associated with current engine\r
468                                         notifySurfaceChanged(engineFormat, engineWidth, engineHeight, false);\r
469 \r
470                                         // connect surface view to current engine\r
471                                         view.surfaceCreated(this.getSurfaceHolder());\r
472                                 }\r
473                                 else {\r
474                                         // update if surface changed when engine wasn't active\r
475                                         notifySurfaceChanged(engineFormat, engineWidth, engineHeight, false);\r
476                                 }\r
477                                 \r
478                                 if (visibleEngines == 1)\r
479                                         app.onResume();\r
480 \r
481                                 notifyPreviewState();\r
482                                 notifyOffsetsChanged();\r
483                         }\r
484                 }\r
485                 \r
486                 \r
487                 public void onPause () {\r
488                         visibleEngines --;\r
489                         if (DEBUG) Log.d(TAG, " > AndroidWallpaperEngine - onPause() " + hashCode() + ", running: " + engines + ", linked: " + (linkedEngine == this) + ", visible: " + visibleEngines);\r
490                         Log.i(TAG, "engine paused");\r
491                         \r
492                         // 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!\r
493                         if (visibleEngines >= engines) {\r
494                                 Log.e(AndroidLiveWallpaperService.TAG, "wallpaper lifecycle error, counted too many visible engines! repairing..");\r
495                                 visibleEngines = Math.max(engines - 1, 0);\r
496                         }\r
497                         \r
498                         if (linkedEngine != null) {\r
499                                 if (visibleEngines == 0)\r
500                                         app.onPause();\r
501                         }\r
502                         \r
503                         if (DEBUG) Log.d(TAG, " > AndroidWallpaperEngine - onPause() done!");\r
504                 }\r
505 \r
506                 \r
507                 /**\r
508                  * Called after surface holder callbacks (ex for GLSurfaceView)!\r
509                  * This is called immediately before a surface is being destroyed. After returning from this call, \r
510                  * you should no longer try to access this surface. If you have a rendering thread that directly \r
511                  * accesses the surface, you must ensure that thread is no longer touching the Surface before \r
512                  * returning from this function.\r
513                  * \r
514                  * Attention!\r
515                  * In some cases GL context may be shutdown right now! and SurfaceHolder.Surface.isVaild = false\r
516                  */\r
517                 @Override\r
518                 public void onSurfaceDestroyed (final SurfaceHolder holder) {\r
519                         engines --;\r
520                         if (DEBUG) Log.d(TAG, " > AndroidWallpaperEngine - onSurfaceDestroyed() " + hashCode()  + ", running: " + engines + " ,linked: " + (linkedEngine == this) + ", isVisible: " + engineIsVisible);\r
521                         Log.i(TAG, "engine surface destroyed");\r
522                         \r
523                         // 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)\r
524                         // it is too late probably - calling on pause causes deadlock\r
525                         //notifyVisibilityChanged(false);\r
526                         \r
527                         // it is too late to call app.onDispose, just free native resources\r
528                         if (engines == 0)\r
529                                 onDeepPauseApplication();\r
530                         \r
531                         // free surface if it belongs to this engine and if it was initialized\r
532                         if (linkedEngine == this && view != null)\r
533                                 view.surfaceDestroyed(holder);\r
534                 \r
535                         //waitingSurfaceChangedEvent = null;\r
536                         engineFormat = 0;\r
537                         engineWidth = 0;\r
538                         engineHeight = 0;\r
539                         \r
540                         // safeguard for other engine callbacks\r
541                         if (engines == 0)\r
542                                 linkedEngine = null;\r
543                         \r
544                         super.onSurfaceDestroyed(holder);\r
545                 }\r
546                 \r
547                 \r
548                 @Override\r
549                 public void onDestroy () {\r
550                         super.onDestroy();\r
551                 }\r
552 \r
553                 // end of lifecycle methods ////////////////////////////////////////////////////////\r
554                 \r
555                 \r
556                 // input\r
557                 \r
558                 @Override\r
559                 public Bundle onCommand (final String pAction, final int pX, final int pY, final int pZ, final Bundle pExtras,\r
560                         final boolean pResultRequested) {\r
561                         if (DEBUG) Log.d(TAG, " > AndroidWallpaperEngine - onCommand(" + pAction + " " + pX + " " + pY + " " + pZ + " " + pExtras + " " + pResultRequested + ")" + ", linked: " + (linkedEngine == this));\r
562                         \r
563                         return super.onCommand(pAction, pX, pY, pZ, pExtras, pResultRequested);\r
564                 }\r
565                 \r
566                 \r
567                 @Override\r
568                 public void onTouchEvent (MotionEvent event) {\r
569                         if (linkedEngine == this) {\r
570                                 app.input.onTouch(null, event);\r
571                         }\r
572                 }\r
573                 \r
574                 \r
575                 // offsets from last onOffsetsChanged\r
576                 boolean offsetsConsumed = true;\r
577                 float xOffset = 0.0f;\r
578                 float yOffset = 0.0f;\r
579                 float xOffsetStep = 0.0f;\r
580                 float yOffsetStep = 0.0f;\r
581                 int xPixelOffset = 0;\r
582                 int yPixelOffset = 0;\r
583 \r
584                 @Override\r
585                 public void onOffsetsChanged (final float xOffset, final float yOffset, final float xOffsetStep, final float yOffsetStep, final int xPixelOffset,\r
586                         final int yPixelOffset) {\r
587 \r
588                         // it spawns too frequent on some devices - its annoying!\r
589                         //if (DEBUG)\r
590                         //      Log.d(TAG, " > AndroidWallpaperEngine - onOffsetChanged(" + xOffset + " " + yOffset + " " + xOffsetStep + " "\r
591                         //              + yOffsetStep + " " + xPixelOffset + " " + yPixelOffset + ") " + hashCode() + ", linkedApp: " + (linkedApp != null));\r
592 \r
593                         this.offsetsConsumed = false;\r
594                         this.xOffset = xOffset;\r
595                         this.yOffset = yOffset;\r
596                         this.xOffsetStep = xOffsetStep;\r
597                         this.yOffsetStep = yOffsetStep;\r
598                         this.xPixelOffset = xPixelOffset;\r
599                         this.yPixelOffset = yPixelOffset;\r
600                         \r
601                         // can fail if linkedApp == null, so we repeat it in Engine.onResume\r
602                         notifyOffsetsChanged();\r
603                         \r
604                         super.onOffsetsChanged(xOffset, yOffset, xOffsetStep, yOffsetStep, xPixelOffset, yPixelOffset);\r
605                 }\r
606                 \r
607                 \r
608                 protected void notifyOffsetsChanged()\r
609                 {\r
610                         if (linkedEngine == this && app.listener instanceof AndroidWallpaperListener) {\r
611                                 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\r
612                                         offsetsConsumed = true;\r
613                                         \r
614                                         app.postRunnable(new Runnable() {\r
615                                                 @Override\r
616                                                 public void run () {\r
617                                                         boolean isCurrent = false;\r
618                                                         synchronized (sync) {\r
619                                                                 isCurrent = (linkedEngine == AndroidWallpaperEngine.this);              // without this app can crash when fast switching between engines (tested!)\r
620                                                         }\r
621                                                         if (isCurrent)\r
622                                                                 ((AndroidWallpaperListener)app.listener).offsetChange(xOffset, yOffset, xOffsetStep, yOffsetStep, xPixelOffset, yPixelOffset);  \r
623                                                 }\r
624                                         });\r
625                                 }\r
626                         }\r
627                 }\r
628                 \r
629                 \r
630                 protected void notifyPreviewState()\r
631                 {\r
632                         // notify preview state to app listener\r
633                         if (linkedEngine == this && app.listener instanceof AndroidWallpaperListener) {\r
634                                 final boolean currentPreviewState = linkedEngine.isPreview();\r
635                                 app.postRunnable(new Runnable() {\r
636                                         @Override\r
637                                         public void run () {\r
638                                                 boolean shouldNotify = false;\r
639                                                 synchronized (sync) {\r
640                                                         if (!isPreviewNotified || notifiedPreviewState != currentPreviewState) {\r
641                                                                 notifiedPreviewState = currentPreviewState;\r
642                                                                 isPreviewNotified = true;\r
643                                                                 shouldNotify = true;\r
644                                                         }\r
645                                                 }\r
646                                                 \r
647                                                 if (shouldNotify) {\r
648                                                         AndroidLiveWallpaper currentApp = app;          // without this app can crash when fast switching between engines (tested!)\r
649                                                         if (currentApp != null)\r
650                                                                 ((AndroidWallpaperListener)currentApp.listener).previewStateChange(currentPreviewState);\r
651                                                 }\r
652                                         }\r
653                                 });\r
654                         }\r
655                 }\r
656         }       \r
657 }\r