WallpaperService.java revision 23ef7b4836d66d31430fedee2aac36275bb30d4b
1/*
2 * Copyright (C) 2009 The Android Open Source Project
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
17package android.service.wallpaper;
18
19import com.android.internal.os.HandlerCaller;
20import com.android.internal.view.BaseIWindow;
21import com.android.internal.view.BaseSurfaceHolder;
22
23import android.app.Service;
24import android.app.WallpaperManager;
25import android.content.BroadcastReceiver;
26import android.content.Context;
27import android.content.Intent;
28import android.content.IntentFilter;
29import android.graphics.Rect;
30import android.os.Bundle;
31import android.os.IBinder;
32import android.os.Looper;
33import android.os.Message;
34import android.os.RemoteException;
35import android.util.Log;
36import android.util.LogPrinter;
37import android.view.Gravity;
38import android.view.IWindowSession;
39import android.view.MotionEvent;
40import android.view.SurfaceHolder;
41import android.view.View;
42import android.view.ViewGroup;
43import android.view.ViewRoot;
44import android.view.WindowManager;
45import android.view.WindowManagerImpl;
46
47/**
48 * A wallpaper service is responsible for showing a live wallpaper behind
49 * applications that would like to sit on top of it.  This service object
50 * itself does very little -- its only purpose is to generate instances of
51 * {@link WallpaperSerice.Engine} as needed.  Implementing a wallpaper thus
52 * involves subclassing from this, subclassing an Engine implementation,
53 * and implementing {@link #onCreateEngine()} to return a new instance of
54 * your engine.
55 */
56public abstract class WallpaperService extends Service {
57    /**
58     * The {@link Intent} that must be declared as handled by the service.
59     */
60    public static final String SERVICE_INTERFACE =
61        "android.service.wallpaper.WallpaperService";
62
63    /**
64     * Name under which a WallpaperService component publishes information
65     * about itself.  This meta-data must reference an XML resource containing
66     * a <code>&lt;{@link android.R.styleable#Wallpaper wallpaper}&gt;</code>
67     * tag.
68     */
69    public static final String SERVICE_META_DATA = "android.service.wallpaper";
70
71    static final String TAG = "WallpaperService";
72    static final boolean DEBUG = false;
73
74    private static final int DO_ATTACH = 10;
75    private static final int DO_DETACH = 20;
76    private static final int DO_SET_DESIRED_SIZE = 30;
77
78    private static final int MSG_UPDATE_SURFACE = 10000;
79    private static final int MSG_VISIBILITY_CHANGED = 10010;
80    private static final int MSG_WALLPAPER_OFFSETS = 10020;
81    private static final int MSG_WALLPAPER_COMMAND = 10025;
82    private static final int MSG_WINDOW_RESIZED = 10030;
83    private static final int MSG_TOUCH_EVENT = 10040;
84
85    private Looper mCallbackLooper;
86
87    static final class WallpaperCommand {
88        String action;
89        int x;
90        int y;
91        int z;
92        Bundle extras;
93        boolean sync;
94    }
95
96    /**
97     * The actual implementation of a wallpaper.  A wallpaper service may
98     * have multiple instances running (for example as a real wallpaper
99     * and as a preview), each of which is represented by its own Engine
100     * instance.  You must implement {@link WallpaperService#onCreateEngine()}
101     * to return your concrete Engine implementation.
102     */
103    public class Engine {
104        IWallpaperEngineWrapper mIWallpaperEngine;
105
106        // Copies from mIWallpaperEngine.
107        HandlerCaller mCaller;
108        IWallpaperConnection mConnection;
109        IBinder mWindowToken;
110
111        boolean mInitializing = true;
112        boolean mVisible;
113        boolean mScreenOn = true;
114        boolean mReportedVisible;
115        boolean mDestroyed;
116
117        // Current window state.
118        boolean mCreated;
119        boolean mIsCreating;
120        boolean mDrawingAllowed;
121        int mWidth;
122        int mHeight;
123        int mFormat;
124        int mType;
125        int mCurWidth;
126        int mCurHeight;
127        int mWindowFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
128        int mCurWindowFlags = mWindowFlags;
129        boolean mDestroyReportNeeded;
130        final Rect mVisibleInsets = new Rect();
131        final Rect mWinFrame = new Rect();
132        final Rect mContentInsets = new Rect();
133
134        final WindowManager.LayoutParams mLayout
135                = new WindowManager.LayoutParams();
136        IWindowSession mSession;
137
138        final Object mLock = new Object();
139        boolean mOffsetMessageEnqueued;
140        float mPendingXOffset;
141        float mPendingYOffset;
142        float mPendingXOffsetStep;
143        float mPendingYOffsetStep;
144        boolean mPendingSync;
145        MotionEvent mPendingMove;
146
147        final BroadcastReceiver mReceiver = new BroadcastReceiver() {
148            @Override
149            public void onReceive(Context context, Intent intent) {
150                if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) {
151                    mScreenOn = true;
152                    reportVisibility();
153                } else if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
154                    mScreenOn = false;
155                    reportVisibility();
156                }
157            }
158        };
159
160        final BaseSurfaceHolder mSurfaceHolder = new BaseSurfaceHolder() {
161
162            @Override
163            public boolean onAllowLockCanvas() {
164                return mDrawingAllowed;
165            }
166
167            @Override
168            public void onRelayoutContainer() {
169                Message msg = mCaller.obtainMessage(MSG_UPDATE_SURFACE);
170                mCaller.sendMessage(msg);
171            }
172
173            @Override
174            public void onUpdateSurface() {
175                Message msg = mCaller.obtainMessage(MSG_UPDATE_SURFACE);
176                mCaller.sendMessage(msg);
177            }
178
179            public boolean isCreating() {
180                return mIsCreating;
181            }
182
183            @Override
184            public void setFixedSize(int width, int height) {
185                throw new UnsupportedOperationException(
186                        "Wallpapers currently only support sizing from layout");
187            }
188
189            public void setKeepScreenOn(boolean screenOn) {
190                throw new UnsupportedOperationException(
191                        "Wallpapers do not support keep screen on");
192            }
193
194        };
195
196        final BaseIWindow mWindow = new BaseIWindow() {
197            @Override
198            public boolean onDispatchPointer(MotionEvent event, long eventTime,
199                    boolean callWhenDone) {
200                synchronized (mLock) {
201                    if (event.getAction() == MotionEvent.ACTION_MOVE) {
202                        if (mPendingMove != null) {
203                            mCaller.removeMessages(MSG_TOUCH_EVENT, mPendingMove);
204                            mPendingMove.recycle();
205                        }
206                        mPendingMove = event;
207                    } else {
208                        mPendingMove = null;
209                    }
210                    Message msg = mCaller.obtainMessageO(MSG_TOUCH_EVENT,
211                            event);
212                    mCaller.sendMessage(msg);
213                }
214                return false;
215            }
216
217            @Override
218            public void resized(int w, int h, Rect coveredInsets,
219                    Rect visibleInsets, boolean reportDraw) {
220                Message msg = mCaller.obtainMessageI(MSG_WINDOW_RESIZED,
221                        reportDraw ? 1 : 0);
222                mCaller.sendMessage(msg);
223            }
224
225            @Override
226            public void dispatchAppVisibility(boolean visible) {
227                // We don't do this in preview mode; we'll let the preview
228                // activity tell us when to run.
229                if (!mIWallpaperEngine.mIsPreview) {
230                    Message msg = mCaller.obtainMessageI(MSG_VISIBILITY_CHANGED,
231                            visible ? 1 : 0);
232                    mCaller.sendMessage(msg);
233                }
234            }
235
236            @Override
237            public void dispatchWallpaperOffsets(float x, float y, float xStep, float yStep,
238                    boolean sync) {
239                synchronized (mLock) {
240                    if (DEBUG) Log.v(TAG, "Dispatch wallpaper offsets: " + x + ", " + y);
241                    mPendingXOffset = x;
242                    mPendingYOffset = y;
243                    mPendingXOffsetStep = xStep;
244                    mPendingYOffsetStep = yStep;
245                    if (sync) {
246                        mPendingSync = true;
247                    }
248                    if (!mOffsetMessageEnqueued) {
249                        mOffsetMessageEnqueued = true;
250                        Message msg = mCaller.obtainMessage(MSG_WALLPAPER_OFFSETS);
251                        mCaller.sendMessage(msg);
252                    }
253                }
254            }
255
256            public void dispatchWallpaperCommand(String action, int x, int y,
257                    int z, Bundle extras, boolean sync) {
258                synchronized (mLock) {
259                    if (DEBUG) Log.v(TAG, "Dispatch wallpaper command: " + x + ", " + y);
260                    WallpaperCommand cmd = new WallpaperCommand();
261                    cmd.action = action;
262                    cmd.x = x;
263                    cmd.y = y;
264                    cmd.z = z;
265                    cmd.extras = extras;
266                    cmd.sync = sync;
267                    Message msg = mCaller.obtainMessage(MSG_WALLPAPER_COMMAND);
268                    msg.obj = cmd;
269                    mCaller.sendMessage(msg);
270                }
271            }
272        };
273
274        /**
275         * Provides access to the surface in which this wallpaper is drawn.
276         */
277        public SurfaceHolder getSurfaceHolder() {
278            return mSurfaceHolder;
279        }
280
281        /**
282         * Convenience for {@link WallpaperManager#getDesiredMinimumWidth()
283         * WallpaperManager.getDesiredMinimumWidth()}, returning the width
284         * that the system would like this wallpaper to run in.
285         */
286        public int getDesiredMinimumWidth() {
287            return mIWallpaperEngine.mReqWidth;
288        }
289
290        /**
291         * Convenience for {@link WallpaperManager#getDesiredMinimumHeight()
292         * WallpaperManager.getDesiredMinimumHeight()}, returning the height
293         * that the system would like this wallpaper to run in.
294         */
295        public int getDesiredMinimumHeight() {
296            return mIWallpaperEngine.mReqHeight;
297        }
298
299        /**
300         * Return whether the wallpaper is currently visible to the user,
301         * this is the last value supplied to
302         * {@link #onVisibilityChanged(boolean)}.
303         */
304        public boolean isVisible() {
305            return mReportedVisible;
306        }
307
308        /**
309         * Returns true if this engine is running in preview mode -- that is,
310         * it is being shown to the user before they select it as the actual
311         * wallpaper.
312         */
313        public boolean isPreview() {
314            return mIWallpaperEngine.mIsPreview;
315        }
316
317        /**
318         * Control whether this wallpaper will receive raw touch events
319         * from the window manager as the user interacts with the window
320         * that is currently displaying the wallpaper.  By default they
321         * are turned off.  If enabled, the events will be received in
322         * {@link #onTouchEvent(MotionEvent)}.
323         */
324        public void setTouchEventsEnabled(boolean enabled) {
325            mWindowFlags = enabled
326                    ? (mWindowFlags&~WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE)
327                    : (mWindowFlags|WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
328            if (mCreated) {
329                updateSurface(false, false);
330            }
331        }
332
333        /**
334         * Called once to initialize the engine.  After returning, the
335         * engine's surface will be created by the framework.
336         */
337        public void onCreate(SurfaceHolder surfaceHolder) {
338        }
339
340        /**
341         * Called right before the engine is going away.  After this the
342         * surface will be destroyed and this Engine object is no longer
343         * valid.
344         */
345        public void onDestroy() {
346        }
347
348        /**
349         * Called to inform you of the wallpaper becoming visible or
350         * hidden.  <em>It is very important that a wallpaper only use
351         * CPU while it is visible.</em>.
352         */
353        public void onVisibilityChanged(boolean visible) {
354        }
355
356        /**
357         * Called as the user performs touch-screen interaction with the
358         * window that is currently showing this wallpaper.  Note that the
359         * events you receive here are driven by the actual application the
360         * user is interacting with, so if it is slow you will get fewer
361         * move events.
362         */
363        public void onTouchEvent(MotionEvent event) {
364        }
365
366        /**
367         * Called to inform you of the wallpaper's offsets changing
368         * within its contain, corresponding to the container's
369         * call to {@link WallpaperManager#setWallpaperOffsets(IBinder, float, float)
370         * WallpaperManager.setWallpaperOffsets()}.
371         */
372        public void onOffsetsChanged(float xOffset, float yOffset,
373                float xOffsetStep, float yOffsetStep,
374                int xPixelOffset, int yPixelOffset) {
375        }
376
377        /**
378         * Process a command that was sent to the wallpaper with
379         * {@link WallpaperManager#sendWallpaperCommand}.
380         * The default implementation does nothing, and always returns null
381         * as the result.
382         *
383         * @param action The name of the command to perform.  This tells you
384         * what to do and how to interpret the rest of the arguments.
385         * @param x Generic integer parameter.
386         * @param y Generic integer parameter.
387         * @param z Generic integer parameter.
388         * @param extras Any additional parameters.
389         * @param resultRequested If true, the caller is requesting that
390         * a result, appropriate for the command, be returned back.
391         * @return If returning a result, create a Bundle and place the
392         * result data in to it.  Otherwise return null.
393         */
394        public Bundle onCommand(String action, int x, int y, int z,
395                Bundle extras, boolean resultRequested) {
396            return null;
397        }
398
399        /**
400         * Called when an application has changed the desired virtual size of
401         * the wallpaper.
402         */
403        public void onDesiredSizeChanged(int desiredWidth, int desiredHeight) {
404        }
405
406        /**
407         * Convenience for {@link SurfaceHolder.Callback#surfaceChanged
408         * SurfaceHolder.Callback.surfaceChanged()}.
409         */
410        public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
411        }
412
413        /**
414         * Convenience for {@link SurfaceHolder.Callback#surfaceCreated
415         * SurfaceHolder.Callback.surfaceCreated()}.
416         */
417        public void onSurfaceCreated(SurfaceHolder holder) {
418        }
419
420        /**
421         * Convenience for {@link SurfaceHolder.Callback#surfaceDestroyed
422         * SurfaceHolder.Callback.surfaceDestroyed()}.
423         */
424        public void onSurfaceDestroyed(SurfaceHolder holder) {
425        }
426
427        void updateSurface(boolean forceRelayout, boolean forceReport) {
428            if (mDestroyed) {
429                Log.w(TAG, "Ignoring updateSurface: destroyed");
430            }
431
432            int myWidth = mSurfaceHolder.getRequestedWidth();
433            if (myWidth <= 0) myWidth = ViewGroup.LayoutParams.FILL_PARENT;
434            int myHeight = mSurfaceHolder.getRequestedHeight();
435            if (myHeight <= 0) myHeight = ViewGroup.LayoutParams.FILL_PARENT;
436
437            final boolean creating = !mCreated;
438            final boolean formatChanged = mFormat != mSurfaceHolder.getRequestedFormat();
439            boolean sizeChanged = mWidth != myWidth || mHeight != myHeight;
440            final boolean typeChanged = mType != mSurfaceHolder.getRequestedType();
441            final boolean flagsChanged = mCurWindowFlags != mWindowFlags;
442            if (forceRelayout || creating || formatChanged || sizeChanged
443                    || typeChanged || flagsChanged) {
444
445                if (DEBUG) Log.v(TAG, "Changes: creating=" + creating
446                        + " format=" + formatChanged + " size=" + sizeChanged);
447
448                try {
449                    mWidth = myWidth;
450                    mHeight = myHeight;
451                    mFormat = mSurfaceHolder.getRequestedFormat();
452                    mType = mSurfaceHolder.getRequestedType();
453
454                    mLayout.x = 0;
455                    mLayout.y = 0;
456                    mLayout.width = myWidth;
457                    mLayout.height = myHeight;
458
459                    mLayout.format = mFormat;
460
461                    mCurWindowFlags = mWindowFlags;
462                    mLayout.flags = mWindowFlags
463                            | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
464                            | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
465                            | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
466                            ;
467
468                    mLayout.memoryType = mType;
469                    mLayout.token = mWindowToken;
470
471                    if (!mCreated) {
472                        mLayout.type = mIWallpaperEngine.mWindowType;
473                        mLayout.gravity = Gravity.LEFT|Gravity.TOP;
474                        mLayout.setTitle(WallpaperService.this.getClass().getName());
475                        mLayout.windowAnimations =
476                                com.android.internal.R.style.Animation_Wallpaper;
477                        mSession.add(mWindow, mLayout, View.VISIBLE, mContentInsets);
478                    }
479
480                    mSurfaceHolder.mSurfaceLock.lock();
481                    mDrawingAllowed = true;
482
483                    final int relayoutResult = mSession.relayout(
484                        mWindow, mLayout, mWidth, mHeight,
485                            View.VISIBLE, false, mWinFrame, mContentInsets,
486                            mVisibleInsets, mSurfaceHolder.mSurface);
487
488                    if (DEBUG) Log.v(TAG, "New surface: " + mSurfaceHolder.mSurface
489                            + ", frame=" + mWinFrame);
490
491                    int w = mWinFrame.width();
492                    if (mCurWidth != w) {
493                        sizeChanged = true;
494                        mCurWidth = w;
495                    }
496                    int h = mWinFrame.height();
497                    if (mCurHeight != h) {
498                        sizeChanged = true;
499                        mCurHeight = h;
500                    }
501
502                    mSurfaceHolder.mSurfaceLock.unlock();
503
504                    try {
505                        mDestroyReportNeeded = true;
506
507                        SurfaceHolder.Callback callbacks[] = null;
508                        synchronized (mSurfaceHolder.mCallbacks) {
509                            final int N = mSurfaceHolder.mCallbacks.size();
510                            if (N > 0) {
511                                callbacks = new SurfaceHolder.Callback[N];
512                                mSurfaceHolder.mCallbacks.toArray(callbacks);
513                            }
514                        }
515
516                        if (!mCreated) {
517                            mIsCreating = true;
518                            if (DEBUG) Log.v(TAG, "onSurfaceCreated("
519                                    + mSurfaceHolder + "): " + this);
520                            onSurfaceCreated(mSurfaceHolder);
521                            if (callbacks != null) {
522                                for (SurfaceHolder.Callback c : callbacks) {
523                                    c.surfaceCreated(mSurfaceHolder);
524                                }
525                            }
526                        }
527                        if (forceReport || creating || formatChanged || sizeChanged) {
528                            if (DEBUG) {
529                                RuntimeException e = new RuntimeException();
530                                e.fillInStackTrace();
531                                Log.w(TAG, "forceReport=" + forceReport + " creating=" + creating
532                                        + " formatChanged=" + formatChanged
533                                        + " sizeChanged=" + sizeChanged, e);
534                            }
535                            if (DEBUG) Log.v(TAG, "onSurfaceChanged("
536                                    + mSurfaceHolder + ", " + mFormat
537                                    + ", " + mCurWidth + ", " + mCurHeight
538                                    + "): " + this);
539                            onSurfaceChanged(mSurfaceHolder, mFormat,
540                                    mCurWidth, mCurHeight);
541                            if (callbacks != null) {
542                                for (SurfaceHolder.Callback c : callbacks) {
543                                    c.surfaceChanged(mSurfaceHolder, mFormat,
544                                            mCurWidth, mCurHeight);
545                                }
546                            }
547                        }
548                    } finally {
549                        mIsCreating = false;
550                        mCreated = true;
551                        if (creating || (relayoutResult&WindowManagerImpl.RELAYOUT_FIRST_TIME) != 0) {
552                            mSession.finishDrawing(mWindow);
553                        }
554                    }
555                } catch (RemoteException ex) {
556                }
557                if (DEBUG) Log.v(
558                    TAG, "Layout: x=" + mLayout.x + " y=" + mLayout.y +
559                    " w=" + mLayout.width + " h=" + mLayout.height);
560            }
561        }
562
563        void attach(IWallpaperEngineWrapper wrapper) {
564            if (DEBUG) Log.v(TAG, "attach: " + this + " wrapper=" + wrapper);
565            if (mDestroyed) {
566                return;
567            }
568
569            mIWallpaperEngine = wrapper;
570            mCaller = wrapper.mCaller;
571            mConnection = wrapper.mConnection;
572            mWindowToken = wrapper.mWindowToken;
573            mSurfaceHolder.setSizeFromLayout();
574            mInitializing = true;
575            mSession = ViewRoot.getWindowSession(getMainLooper());
576            mWindow.setSession(mSession);
577
578            IntentFilter filter = new IntentFilter();
579            filter.addAction(Intent.ACTION_SCREEN_ON);
580            filter.addAction(Intent.ACTION_SCREEN_OFF);
581            registerReceiver(mReceiver, filter);
582
583            if (DEBUG) Log.v(TAG, "onCreate(): " + this);
584            onCreate(mSurfaceHolder);
585
586            mInitializing = false;
587            updateSurface(false, false);
588        }
589
590        void doDesiredSizeChanged(int desiredWidth, int desiredHeight) {
591            if (!mDestroyed) {
592                if (DEBUG) Log.v(TAG, "onDesiredSizeChanged("
593                        + desiredWidth + "," + desiredHeight + "): " + this);
594                onDesiredSizeChanged(desiredWidth, desiredHeight);
595            }
596        }
597
598        void doVisibilityChanged(boolean visible) {
599            mVisible = visible;
600            reportVisibility();
601        }
602
603        void reportVisibility() {
604            if (!mDestroyed) {
605                boolean visible = mVisible && mScreenOn;
606                if (mReportedVisible != visible) {
607                    mReportedVisible = visible;
608                    if (DEBUG) Log.v(TAG, "onVisibilityChanged(" + visible
609                            + "): " + this);
610                    onVisibilityChanged(visible);
611                }
612            }
613        }
614
615        void doOffsetsChanged() {
616            if (mDestroyed) {
617                return;
618            }
619
620            float xOffset;
621            float yOffset;
622            float xOffsetStep;
623            float yOffsetStep;
624            boolean sync;
625            synchronized (mLock) {
626                xOffset = mPendingXOffset;
627                yOffset = mPendingYOffset;
628                xOffsetStep = mPendingXOffsetStep;
629                yOffsetStep = mPendingYOffsetStep;
630                sync = mPendingSync;
631                mPendingSync = false;
632                mOffsetMessageEnqueued = false;
633            }
634            if (DEBUG) Log.v(TAG, "Offsets change in " + this
635                    + ": " + xOffset + "," + yOffset);
636            final int availw = mIWallpaperEngine.mReqWidth-mCurWidth;
637            final int xPixels = availw > 0 ? -(int)(availw*xOffset+.5f) : 0;
638            final int availh = mIWallpaperEngine.mReqHeight-mCurHeight;
639            final int yPixels = availh > 0 ? -(int)(availh*yOffset+.5f) : 0;
640            onOffsetsChanged(xOffset, yOffset, xOffsetStep, yOffsetStep, xPixels, yPixels);
641
642            if (sync) {
643                try {
644                    if (DEBUG) Log.v(TAG, "Reporting offsets change complete");
645                    mSession.wallpaperOffsetsComplete(mWindow.asBinder());
646                } catch (RemoteException e) {
647                }
648            }
649        }
650
651        void doCommand(WallpaperCommand cmd) {
652            Bundle result;
653            if (!mDestroyed) {
654                result = onCommand(cmd.action, cmd.x, cmd.y, cmd.z,
655                        cmd.extras, cmd.sync);
656            } else {
657                result = null;
658            }
659            if (cmd.sync) {
660                try {
661                    if (DEBUG) Log.v(TAG, "Reporting command complete");
662                    mSession.wallpaperCommandComplete(mWindow.asBinder(), result);
663                } catch (RemoteException e) {
664                }
665            }
666        }
667
668        void detach() {
669            mDestroyed = true;
670
671            if (mVisible) {
672                mVisible = false;
673                if (DEBUG) Log.v(TAG, "onVisibilityChanged(false): " + this);
674                onVisibilityChanged(false);
675            }
676
677            if (mDestroyReportNeeded) {
678                mDestroyReportNeeded = false;
679                SurfaceHolder.Callback callbacks[];
680                synchronized (mSurfaceHolder.mCallbacks) {
681                    callbacks = new SurfaceHolder.Callback[
682                            mSurfaceHolder.mCallbacks.size()];
683                    mSurfaceHolder.mCallbacks.toArray(callbacks);
684                }
685                for (SurfaceHolder.Callback c : callbacks) {
686                    c.surfaceDestroyed(mSurfaceHolder);
687                }
688                if (DEBUG) Log.v(TAG, "onSurfaceDestroyed("
689                        + mSurfaceHolder + "): " + this);
690                onSurfaceDestroyed(mSurfaceHolder);
691            }
692
693            if (DEBUG) Log.v(TAG, "onDestroy(): " + this);
694            onDestroy();
695
696            unregisterReceiver(mReceiver);
697
698            if (mCreated) {
699                try {
700                    mSession.remove(mWindow);
701                } catch (RemoteException e) {
702                }
703                mSurfaceHolder.mSurface.release();
704                mCreated = false;
705            }
706        }
707    }
708
709    class IWallpaperEngineWrapper extends IWallpaperEngine.Stub
710            implements HandlerCaller.Callback {
711        private final HandlerCaller mCaller;
712
713        final IWallpaperConnection mConnection;
714        final IBinder mWindowToken;
715        final int mWindowType;
716        final boolean mIsPreview;
717        int mReqWidth;
718        int mReqHeight;
719
720        Engine mEngine;
721
722        IWallpaperEngineWrapper(WallpaperService context,
723                IWallpaperConnection conn, IBinder windowToken,
724                int windowType, boolean isPreview, int reqWidth, int reqHeight) {
725            if (DEBUG && mCallbackLooper != null) {
726                mCallbackLooper.setMessageLogging(new LogPrinter(Log.VERBOSE, TAG));
727            }
728            mCaller = new HandlerCaller(context,
729                    mCallbackLooper != null
730                            ? mCallbackLooper : context.getMainLooper(),
731                    this);
732            mConnection = conn;
733            mWindowToken = windowToken;
734            mWindowType = windowType;
735            mIsPreview = isPreview;
736            mReqWidth = reqWidth;
737            mReqHeight = reqHeight;
738
739            Message msg = mCaller.obtainMessage(DO_ATTACH);
740            mCaller.sendMessage(msg);
741        }
742
743        public void setDesiredSize(int width, int height) {
744            Message msg = mCaller.obtainMessageII(DO_SET_DESIRED_SIZE, width, height);
745            mCaller.sendMessage(msg);
746        }
747
748        public void setVisibility(boolean visible) {
749            Message msg = mCaller.obtainMessageI(MSG_VISIBILITY_CHANGED,
750                    visible ? 1 : 0);
751            mCaller.sendMessage(msg);
752        }
753
754        public void dispatchPointer(MotionEvent event) {
755            if (mEngine != null) {
756                mEngine.mWindow.onDispatchPointer(event, event.getEventTime(), false);
757            }
758        }
759
760        public void destroy() {
761            Message msg = mCaller.obtainMessage(DO_DETACH);
762            mCaller.sendMessage(msg);
763        }
764
765        public void executeMessage(Message message) {
766            switch (message.what) {
767                case DO_ATTACH: {
768                    try {
769                        mConnection.attachEngine(this);
770                    } catch (RemoteException e) {
771                        Log.w(TAG, "Wallpaper host disappeared", e);
772                        return;
773                    }
774                    Engine engine = onCreateEngine();
775                    mEngine = engine;
776                    engine.attach(this);
777                    return;
778                }
779                case DO_DETACH: {
780                    mEngine.detach();
781                    return;
782                }
783                case DO_SET_DESIRED_SIZE: {
784                    mEngine.doDesiredSizeChanged(message.arg1, message.arg2);
785                    return;
786                }
787                case MSG_UPDATE_SURFACE:
788                    mEngine.updateSurface(true, false);
789                    break;
790                case MSG_VISIBILITY_CHANGED:
791                    if (DEBUG) Log.v(TAG, "Visibility change in " + mEngine
792                            + ": " + message.arg1);
793                    mEngine.doVisibilityChanged(message.arg1 != 0);
794                    break;
795                case MSG_WALLPAPER_OFFSETS: {
796                    mEngine.doOffsetsChanged();
797                } break;
798                case MSG_WALLPAPER_COMMAND: {
799                    WallpaperCommand cmd = (WallpaperCommand)message.obj;
800                    mEngine.doCommand(cmd);
801                } break;
802                case MSG_WINDOW_RESIZED: {
803                    final boolean reportDraw = message.arg1 != 0;
804                    mEngine.updateSurface(true, false);
805                    if (reportDraw) {
806                        try {
807                            mEngine.mSession.finishDrawing(mEngine.mWindow);
808                        } catch (RemoteException e) {
809                        }
810                    }
811                } break;
812                case MSG_TOUCH_EVENT: {
813                    MotionEvent ev = (MotionEvent)message.obj;
814                    synchronized (mEngine.mLock) {
815                        if (mEngine.mPendingMove == ev) {
816                            mEngine.mPendingMove = null;
817                        }
818                    }
819                    if (DEBUG) Log.v(TAG, "Delivering touch event: " + ev);
820                    mEngine.onTouchEvent(ev);
821                    ev.recycle();
822                } break;
823                default :
824                    Log.w(TAG, "Unknown message type " + message.what);
825            }
826        }
827    }
828
829    /**
830     * Implements the internal {@link IWallpaperService} interface to convert
831     * incoming calls to it back to calls on an {@link WallpaperService}.
832     */
833    class IWallpaperServiceWrapper extends IWallpaperService.Stub {
834        private final WallpaperService mTarget;
835
836        public IWallpaperServiceWrapper(WallpaperService context) {
837            mTarget = context;
838        }
839
840        public void attach(IWallpaperConnection conn, IBinder windowToken,
841                int windowType, boolean isPreview, int reqWidth, int reqHeight) {
842            new IWallpaperEngineWrapper(mTarget, conn, windowToken,
843                    windowType, isPreview, reqWidth, reqHeight);
844        }
845    }
846
847    /**
848     * Implement to return the implementation of the internal accessibility
849     * service interface.  Subclasses should not override.
850     */
851    @Override
852    public final IBinder onBind(Intent intent) {
853        return new IWallpaperServiceWrapper(this);
854    }
855
856    /**
857     * This allows subclasses to change the thread that most callbacks
858     * occur on.  Currently hidden because it is mostly needed for the
859     * image wallpaper (which runs in the system process and doesn't want
860     * to get stuck running on that seriously in use main thread).  Not
861     * exposed right now because the semantics of this are not totally
862     * well defined and some callbacks can still happen on the main thread).
863     * @hide
864     */
865    public void setCallbackLooper(Looper looper) {
866        mCallbackLooper = looper;
867    }
868
869    /**
870     * Must be implemented to return a new instance of the wallpaper's engine.
871     * Note that multiple instances may be active at the same time, such as
872     * when the wallpaper is currently set as the active wallpaper and the user
873     * is in the wallpaper picker viewing a preview of it as well.
874     */
875    public abstract Engine onCreateEngine();
876}
877